{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "ef0a4ee4",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'C:\\\\Users\\\\IDEA'"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "%pwd"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "japanese-margin",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'C:\\\\Users\\\\IDEA'"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import os\n",
    "os.getcwd()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "executive-insulin",
   "metadata": {},
   "outputs": [],
   "source": [
    "os.chdir('E:\\\\experimentos\\\\2025\\\\Dataset_1')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "0888b480",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'E:\\\\experimentos\\\\2025\\\\Dataset_1'"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "%pwd"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "d29220ca",
   "metadata": {},
   "outputs": [],
   "source": [
    "from tensorflow import keras\n",
    "#keras.__version__\n",
    "import os, shutil"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "d979c4e9",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "E:\\experimentos\\2025\\Dataset_1\n",
      "E:\\experimentos\\2025\\Dataset_1\\conv\n",
      "True\n"
     ]
    }
   ],
   "source": [
    "original_dir = os.path.abspath('E:\\\\experimentos\\\\2025\\\\Dataset_1')\n",
    "# The directory where we will\n",
    "# store our smaller dataset\n",
    "base_dir = os.path.join(original_dir, \"conv\")\n",
    "print(original_dir)\n",
    "print(base_dir)\n",
    "# added by reviewer\n",
    "isdir = os.path.isdir(base_dir) \n",
    "print(isdir) \n",
    "\n",
    "if isdir ==True:\n",
    "    shutil.rmtree(base_dir)\n",
    "\n",
    "os.mkdir(base_dir)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "6b486b83",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Directories for our training,\n",
    "# validation and test splits\n",
    "train_dir = os.path.join(base_dir, 'train')\n",
    "os.mkdir(train_dir)\n",
    "validation_dir = os.path.join(base_dir, 'validation')\n",
    "os.mkdir(validation_dir)\n",
    "\n",
    "\n",
    "\n",
    "# Directory with our training croc pictures\n",
    "train_croc_dir = os.path.join(train_dir, 'CS1')\n",
    "os.mkdir(train_croc_dir)\n",
    "\n",
    "# Directory with our training cm pictures\n",
    "train_cm_dir = os.path.join(train_dir, 'SF')\n",
    "os.mkdir(train_cm_dir)\n",
    "\n",
    "\n",
    "\n",
    "# Directory with our validation croc pictures\n",
    "validation_croc_dir = os.path.join(validation_dir, 'CS1')\n",
    "os.mkdir(validation_croc_dir)\n",
    "\n",
    "# Directory with our validation cm pictures\n",
    "validation_cm_dir = os.path.join(validation_dir, 'SF')\n",
    "os.mkdir(validation_cm_dir)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "2cf0466a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "32\n",
      "['CS1.21.bmp', 'CS1.41.bmp', 'CS1.32.bmp', 'CS1.18.bmp', 'CS1.26.bmp', 'CS1.15.bmp', 'CS1.24.bmp', 'CS1.40.bmp', 'CS1.6.bmp', 'CS1.22.bmp', 'CS1.3.bmp', 'CS1.38.bmp', 'CS1.42.bmp', 'CS1.16.bmp', 'CS1.35.bmp', 'CS1.28.bmp', 'CS1.5.bmp', 'CS1.14.bmp', 'CS1.8.bmp', 'CS1.9.bmp', 'CS1.25.bmp', 'CS1.12.bmp', 'CS1.43.bmp', 'CS1.19.bmp', 'CS1.4.bmp', 'CS1.39.bmp', 'CS1.1.bmp', 'CS1.11.bmp', 'CS1.23.bmp', 'CS1.7.bmp', 'CS1.34.bmp', 'CS1.20.bmp']\n",
      "14\n",
      "['CS1.37.bmp', 'CS1.33.bmp', 'CS1.44.bmp', 'CS1.10.bmp', 'CS1.17.bmp', 'CS1.31.bmp', 'CS1.29.bmp', 'CS1.13.bmp', 'CS1.0.bmp', 'CS1.45.bmp', 'CS1.30.bmp', 'CS1.27.bmp', 'CS1.2.bmp', 'CS1.36.bmp']\n",
      "342\n",
      "['SF.180.bmp', 'SF.287.bmp', 'SF.289.bmp', 'SF.397.bmp', 'SF.303.bmp', 'SF.330.bmp', 'SF.29.bmp', 'SF.63.bmp', 'SF.342.bmp', 'SF.485.bmp', 'SF.8.bmp', 'SF.410.bmp', 'SF.257.bmp', 'SF.260.bmp', 'SF.375.bmp', 'SF.161.bmp', 'SF.109.bmp', 'SF.466.bmp', 'SF.96.bmp', 'SF.384.bmp', 'SF.64.bmp', 'SF.481.bmp', 'SF.443.bmp', 'SF.131.bmp', 'SF.128.bmp', 'SF.11.bmp', 'SF.52.bmp', 'SF.392.bmp', 'SF.190.bmp', 'SF.26.bmp', 'SF.58.bmp', 'SF.27.bmp', 'SF.201.bmp', 'SF.366.bmp', 'SF.407.bmp', 'SF.404.bmp', 'SF.324.bmp', 'SF.321.bmp', 'SF.35.bmp', 'SF.434.bmp', 'SF.66.bmp', 'SF.387.bmp', 'SF.479.bmp', 'SF.262.bmp', 'SF.323.bmp', 'SF.231.bmp', 'SF.320.bmp', 'SF.406.bmp', 'SF.326.bmp', 'SF.182.bmp', 'SF.111.bmp', 'SF.202.bmp', 'SF.343.bmp', 'SF.104.bmp', 'SF.313.bmp', 'SF.269.bmp', 'SF.141.bmp', 'SF.356.bmp', 'SF.10.bmp', 'SF.188.bmp', 'SF.0.bmp', 'SF.420.bmp', 'SF.34.bmp', 'SF.31.bmp', 'SF.60.bmp', 'SF.355.bmp', 'SF.79.bmp', 'SF.386.bmp', 'SF.206.bmp', 'SF.483.bmp', 'SF.459.bmp', 'SF.172.bmp', 'SF.385.bmp', 'SF.235.bmp', 'SF.401.bmp', 'SF.94.bmp', 'SF.246.bmp', 'SF.360.bmp', 'SF.53.bmp', 'SF.249.bmp', 'SF.170.bmp', 'SF.450.bmp', 'SF.171.bmp', 'SF.149.bmp', 'SF.261.bmp', 'SF.362.bmp', 'SF.340.bmp', 'SF.20.bmp', 'SF.62.bmp', 'SF.174.bmp', 'SF.358.bmp', 'SF.159.bmp', 'SF.263.bmp', 'SF.398.bmp', 'SF.327.bmp', 'SF.408.bmp', 'SF.468.bmp', 'SF.369.bmp', 'SF.135.bmp', 'SF.285.bmp', 'SF.118.bmp', 'SF.422.bmp', 'SF.158.bmp', 'SF.74.bmp', 'SF.306.bmp', 'SF.28.bmp', 'SF.39.bmp', 'SF.258.bmp', 'SF.335.bmp', 'SF.317.bmp', 'SF.6.bmp', 'SF.402.bmp', 'SF.312.bmp', 'SF.46.bmp', 'SF.143.bmp', 'SF.284.bmp', 'SF.193.bmp', 'SF.378.bmp', 'SF.390.bmp', 'SF.354.bmp', 'SF.329.bmp', 'SF.110.bmp', 'SF.349.bmp', 'SF.5.bmp', 'SF.19.bmp', 'SF.309.bmp', 'SF.383.bmp', 'SF.183.bmp', 'SF.302.bmp', 'SF.405.bmp', 'SF.117.bmp', 'SF.227.bmp', 'SF.243.bmp', 'SF.372.bmp', 'SF.44.bmp', 'SF.337.bmp', 'SF.115.bmp', 'SF.435.bmp', 'SF.265.bmp', 'SF.37.bmp', 'SF.15.bmp', 'SF.399.bmp', 'SF.173.bmp', 'SF.239.bmp', 'SF.331.bmp', 'SF.394.bmp', 'SF.130.bmp', 'SF.137.bmp', 'SF.482.bmp', 'SF.42.bmp', 'SF.88.bmp', 'SF.30.bmp', 'SF.322.bmp', 'SF.328.bmp', 'SF.153.bmp', 'SF.454.bmp', 'SF.439.bmp', 'SF.213.bmp', 'SF.164.bmp', 'SF.9.bmp', 'SF.162.bmp', 'SF.288.bmp', 'SF.293.bmp', 'SF.352.bmp', 'SF.478.bmp', 'SF.133.bmp', 'SF.48.bmp', 'SF.147.bmp', 'SF.86.bmp', 'SF.51.bmp', 'SF.296.bmp', 'SF.353.bmp', 'SF.374.bmp', 'SF.112.bmp', 'SF.283.bmp', 'SF.276.bmp', 'SF.241.bmp', 'SF.259.bmp', 'SF.242.bmp', 'SF.456.bmp', 'SF.56.bmp', 'SF.13.bmp', 'SF.476.bmp', 'SF.24.bmp', 'SF.200.bmp', 'SF.208.bmp', 'SF.380.bmp', 'SF.318.bmp', 'SF.432.bmp', 'SF.238.bmp', 'SF.95.bmp', 'SF.156.bmp', 'SF.101.bmp', 'SF.189.bmp', 'SF.325.bmp', 'SF.473.bmp', 'SF.418.bmp', 'SF.282.bmp', 'SF.291.bmp', 'SF.4.bmp', 'SF.300.bmp', 'SF.430.bmp', 'SF.152.bmp', 'SF.297.bmp', 'SF.144.bmp', 'SF.414.bmp', 'SF.264.bmp', 'SF.377.bmp', 'SF.43.bmp', 'SF.203.bmp', 'SF.424.bmp', 'SF.214.bmp', 'SF.365.bmp', 'SF.333.bmp', 'SF.279.bmp', 'SF.138.bmp', 'SF.176.bmp', 'SF.21.bmp', 'SF.220.bmp', 'SF.150.bmp', 'SF.413.bmp', 'SF.272.bmp', 'SF.367.bmp', 'SF.350.bmp', 'SF.70.bmp', 'SF.447.bmp', 'SF.346.bmp', 'SF.87.bmp', 'SF.361.bmp', 'SF.113.bmp', 'SF.103.bmp', 'SF.67.bmp', 'SF.305.bmp', 'SF.467.bmp', 'SF.129.bmp', 'SF.75.bmp', 'SF.370.bmp', 'SF.119.bmp', 'SF.280.bmp', 'SF.400.bmp', 'SF.427.bmp', 'SF.270.bmp', 'SF.194.bmp', 'SF.252.bmp', 'SF.116.bmp', 'SF.92.bmp', 'SF.65.bmp', 'SF.373.bmp', 'SF.14.bmp', 'SF.210.bmp', 'SF.363.bmp', 'SF.204.bmp', 'SF.108.bmp', 'SF.197.bmp', 'SF.472.bmp', 'SF.102.bmp', 'SF.458.bmp', 'SF.209.bmp', 'SF.217.bmp', 'SF.107.bmp', 'SF.3.bmp', 'SF.487.bmp', 'SF.186.bmp', 'SF.379.bmp', 'SF.271.bmp', 'SF.357.bmp', 'SF.304.bmp', 'SF.301.bmp', 'SF.237.bmp', 'SF.157.bmp', 'SF.396.bmp', 'SF.106.bmp', 'SF.218.bmp', 'SF.433.bmp', 'SF.359.bmp', 'SF.93.bmp', 'SF.319.bmp', 'SF.98.bmp', 'SF.339.bmp', 'SF.465.bmp', 'SF.72.bmp', 'SF.244.bmp', 'SF.83.bmp', 'SF.146.bmp', 'SF.54.bmp', 'SF.425.bmp', 'SF.23.bmp', 'SF.45.bmp', 'SF.148.bmp', 'SF.126.bmp', 'SF.368.bmp', 'SF.151.bmp', 'SF.59.bmp', 'SF.12.bmp', 'SF.371.bmp', 'SF.294.bmp', 'SF.100.bmp', 'SF.411.bmp', 'SF.165.bmp', 'SF.121.bmp', 'SF.78.bmp', 'SF.382.bmp', 'SF.347.bmp', 'SF.168.bmp', 'SF.216.bmp', 'SF.315.bmp', 'SF.132.bmp', 'SF.175.bmp', 'SF.388.bmp', 'SF.417.bmp', 'SF.455.bmp', 'SF.234.bmp', 'SF.461.bmp', 'SF.248.bmp', 'SF.232.bmp', 'SF.311.bmp', 'SF.55.bmp', 'SF.77.bmp', 'SF.47.bmp', 'SF.179.bmp', 'SF.25.bmp', 'SF.477.bmp', 'SF.431.bmp', 'SF.224.bmp', 'SF.1.bmp', 'SF.412.bmp', 'SF.389.bmp', 'SF.50.bmp', 'SF.441.bmp', 'SF.286.bmp', 'SF.376.bmp', 'SF.426.bmp', 'SF.198.bmp', 'SF.275.bmp', 'SF.391.bmp', 'SF.419.bmp', 'SF.277.bmp', 'SF.457.bmp', 'SF.69.bmp', 'SF.205.bmp', 'SF.480.bmp', 'SF.470.bmp']\n",
      "146\n",
      "['SF.444.bmp', 'SF.32.bmp', 'SF.255.bmp', 'SF.460.bmp', 'SF.484.bmp', 'SF.471.bmp', 'SF.139.bmp', 'SF.268.bmp', 'SF.123.bmp', 'SF.344.bmp', 'SF.253.bmp', 'SF.199.bmp', 'SF.81.bmp', 'SF.437.bmp', 'SF.207.bmp', 'SF.229.bmp', 'SF.453.bmp', 'SF.40.bmp', 'SF.124.bmp', 'SF.163.bmp', 'SF.169.bmp', 'SF.438.bmp', 'SF.364.bmp', 'SF.278.bmp', 'SF.436.bmp', 'SF.474.bmp', 'SF.222.bmp', 'SF.469.bmp', 'SF.177.bmp', 'SF.245.bmp', 'SF.90.bmp', 'SF.451.bmp', 'SF.332.bmp', 'SF.448.bmp', 'SF.84.bmp', 'SF.409.bmp', 'SF.445.bmp', 'SF.136.bmp', 'SF.61.bmp', 'SF.91.bmp', 'SF.267.bmp', 'SF.316.bmp', 'SF.73.bmp', 'SF.140.bmp', 'SF.348.bmp', 'SF.125.bmp', 'SF.228.bmp', 'SF.212.bmp', 'SF.221.bmp', 'SF.178.bmp', 'SF.97.bmp', 'SF.85.bmp', 'SF.226.bmp', 'SF.49.bmp', 'SF.429.bmp', 'SF.57.bmp', 'SF.403.bmp', 'SF.307.bmp', 'SF.191.bmp', 'SF.290.bmp', 'SF.196.bmp', 'SF.76.bmp', 'SF.41.bmp', 'SF.273.bmp', 'SF.36.bmp', 'SF.336.bmp', 'SF.310.bmp', 'SF.154.bmp', 'SF.254.bmp', 'SF.486.bmp', 'SF.18.bmp', 'SF.428.bmp', 'SF.211.bmp', 'SF.114.bmp', 'SF.80.bmp', 'SF.256.bmp', 'SF.166.bmp', 'SF.299.bmp', 'SF.475.bmp', 'SF.33.bmp', 'SF.240.bmp', 'SF.223.bmp', 'SF.230.bmp', 'SF.281.bmp', 'SF.341.bmp', 'SF.160.bmp', 'SF.442.bmp', 'SF.395.bmp', 'SF.187.bmp', 'SF.274.bmp', 'SF.449.bmp', 'SF.99.bmp', 'SF.446.bmp', 'SF.120.bmp', 'SF.2.bmp', 'SF.298.bmp', 'SF.464.bmp', 'SF.192.bmp', 'SF.314.bmp', 'SF.225.bmp', 'SF.122.bmp', 'SF.462.bmp', 'SF.393.bmp', 'SF.452.bmp', 'SF.68.bmp', 'SF.185.bmp', 'SF.338.bmp', 'SF.155.bmp', 'SF.351.bmp', 'SF.89.bmp', 'SF.233.bmp', 'SF.134.bmp', 'SF.345.bmp', 'SF.423.bmp', 'SF.145.bmp', 'SF.463.bmp', 'SF.195.bmp', 'SF.181.bmp', 'SF.308.bmp', 'SF.71.bmp', 'SF.440.bmp', 'SF.215.bmp', 'SF.22.bmp', 'SF.184.bmp', 'SF.381.bmp', 'SF.127.bmp', 'SF.38.bmp', 'SF.167.bmp', 'SF.250.bmp', 'SF.266.bmp', 'SF.17.bmp', 'SF.82.bmp', 'SF.415.bmp', 'SF.334.bmp', 'SF.142.bmp', 'SF.421.bmp', 'SF.251.bmp', 'SF.416.bmp', 'SF.236.bmp', 'SF.105.bmp', 'SF.7.bmp', 'SF.295.bmp', 'SF.247.bmp', 'SF.219.bmp', 'SF.16.bmp', 'SF.292.bmp']\n"
     ]
    }
   ],
   "source": [
    "\n",
    "\n",
    "from tensorflow.keras.preprocessing.image import ImageDataGenerator\n",
    "# randomize order of image locations\n",
    "# this will help model in not being data sequence dependent\n",
    "\n",
    "# Copy first CM images to train_lion_dir\n",
    "import random\n",
    "    \n",
    "    \n",
    "\n",
    "# Copy first TM images to train_croc_dir # \n",
    "fnames = ['CS1.{}.bmp'.format(i) for i in range(46)]\n",
    "random.seed(10)\n",
    "random.shuffle(fnames)\n",
    "fnamest = fnames[0:32]\n",
    "print(len(fnamest))\n",
    "print(fnamest)\n",
    "\n",
    "for fname in fnamest:\n",
    "    src = os.path.join(original_dir, fname)\n",
    "    dst = os.path.join(train_croc_dir, fname)\n",
    "    shutil.copyfile(src, dst)\n",
    "        \n",
    "# Copy next 10 TM images to validation_croc_dir\n",
    "fnamesv = fnames[32:46]\n",
    "print(len(fnamesv))\n",
    "print(fnamesv)\n",
    "for fname in fnamesv:\n",
    "    src = os.path.join(original_dir, fname)\n",
    "    dst = os.path.join(validation_croc_dir, fname)\n",
    "    shutil.copyfile(src, dst)\n",
    "         \n",
    "        \n",
    "    \n",
    "# Copy first TM images to train_cm_dir # \n",
    "fnames = ['SF.{}.bmp'.format(i) for i in range(488)]\n",
    "random.seed(10)\n",
    "random.shuffle(fnames)\n",
    "fnamest = fnames[0:342]\n",
    "print(len(fnamest))\n",
    "print(fnamest)\n",
    "\n",
    "for fname in fnamest:\n",
    "    src = os.path.join(original_dir, fname)\n",
    "    dst = os.path.join(train_cm_dir, fname)\n",
    "    shutil.copyfile(src, dst)\n",
    "        \n",
    "# Copy next TM images to validation_cm_dir\n",
    "fnamesv = fnames[342:488]\n",
    "print(len(fnamesv))\n",
    "print(fnamesv)\n",
    "for fname in fnamesv:\n",
    "    src = os.path.join(original_dir, fname)\n",
    "    dst = os.path.join(validation_cm_dir, fname)\n",
    "    shutil.copyfile(src, dst)\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "efe09e3f",
   "metadata": {
    "collapsed": true,
    "jupyter": {
     "outputs_hidden": true
    }
   },
   "outputs": [],
   "source": [
    "#STAGE 1: training and testing sets"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "02b116cf",
   "metadata": {
    "collapsed": true,
    "jupyter": {
     "outputs_hidden": true
    }
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4e98e48e",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "0b730d9a",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Found 374 images belonging to 2 classes.\n",
      "Found 160 images belonging to 2 classes.\n"
     ]
    }
   ],
   "source": [
    "from tensorflow.keras.applications.resnet50 import preprocess_input #for RESNET 50\n",
    "#from tensorflow.keras.applications.imagenet_utils import decode_predictions, preprocess_input#for efficient Net\n",
    "\n",
    "#Let's train our network using data augmentation and dropout:\n",
    "train_datagen = ImageDataGenerator(\n",
    "    rotation_range=40,\n",
    "    width_shift_range=0.2,\n",
    "    height_shift_range=0.2,\n",
    "    shear_range=0.2,\n",
    "    zoom_range=0.2,\n",
    "    horizontal_flip=True,\n",
    "    preprocessing_function= \\\n",
    "    keras.applications.resnet50.preprocess_input)#OJO!!!! imagenet_utils for Efficient Net\n",
    "\n",
    "# Note that the validation data should not be augmented!\n",
    "test_datagen = ImageDataGenerator(preprocessing_function= \\\n",
    "    keras.applications.resnet50.preprocess_input)#OJO!!!\n",
    "\n",
    "train_generator = train_datagen.flow_from_directory(\n",
    "        # This is the target directory\n",
    "        train_dir,\n",
    "        # All images will be resized \n",
    "        target_size=(80, 400),\n",
    "        batch_size=64,\n",
    "        class_mode='categorical')#put \"categorical\" si son más de 2 categorías\n",
    "\n",
    "validation_generator = test_datagen.flow_from_directory(\n",
    "        validation_dir,\n",
    "        target_size=(80, 400),\n",
    "        batch_size=32,\n",
    "        class_mode='categorical',\n",
    "        shuffle=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "62521d75",
   "metadata": {
    "collapsed": true,
    "jupyter": {
     "outputs_hidden": true
    }
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "af7b5cdc",
   "metadata": {
    "collapsed": true,
    "jupyter": {
     "outputs_hidden": true
    }
   },
   "outputs": [],
   "source": [
    "#MODULE 2: CHOOSE MODEL"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0d1d0fac",
   "metadata": {
    "collapsed": true,
    "jupyter": {
     "outputs_hidden": true
    }
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "566a8bbd",
   "metadata": {
    "collapsed": true,
    "jupyter": {
     "outputs_hidden": true
    }
   },
   "outputs": [],
   "source": [
    "#activate Dropout and Early Stopping"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 65,
   "id": "complicated-college",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "WARNING:absl:`lr` is deprecated in Keras optimizer, please use `learning_rate` or use the legacy optimizer, e.g.,tf.keras.optimizers.legacy.SGD.\n"
     ]
    }
   ],
   "source": [
    "# for transfer learning RESNET50 with Dropout\n",
    "import sys\n",
    "from matplotlib import pyplot\n",
    "from tensorflow.keras.applications.resnet50 import ResNet50\n",
    "from keras.models import Model\n",
    "from keras.layers import Dense\n",
    "from keras.layers import Flatten\n",
    "from keras.layers import Dropout\n",
    "from keras.optimizers import SGD\n",
    "from keras.optimizers import Adagrad\n",
    "from keras.preprocessing.image import ImageDataGenerator\n",
    "\n",
    "# define cnn model\n",
    "def define_model():\n",
    "\t# load model\n",
    "\tmodel = ResNet50(include_top=False, input_shape=(80, 400, 3))\n",
    "\t# mark loaded layers as not trainable\n",
    "\tfor layer in model.layers:\n",
    "\t\tlayer.trainable = False\n",
    "\t# add new classifier layers\n",
    "\tflat1 = Flatten()(model.layers[-1].output)\n",
    "\tclass1 = Dense(128, activation='relu', kernel_initializer='he_uniform')(flat1)\n",
    "\tdrop=Dropout(0.3)(class1)\n",
    "\toutput = Dense(2, activation='softmax')(drop)\n",
    "\t# define new model\n",
    "\tmodel = Model(inputs=model.inputs, outputs=output)\n",
    "\t# compile model\n",
    "\topt = SGD(lr=0.001, momentum=0.9)\n",
    "\t#opt = Adagrad(lr=0.001)\n",
    "\tmodel.compile(optimizer=opt, loss='categorical_crossentropy', metrics=['accuracy'])\n",
    "\treturn model\n",
    "\n",
    "# define model\n",
    "model = define_model()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "50a09b9a",
   "metadata": {
    "collapsed": true,
    "jupyter": {
     "outputs_hidden": true
    }
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6fc143a8",
   "metadata": {
    "collapsed": true,
    "jupyter": {
     "outputs_hidden": true
    }
   },
   "outputs": [],
   "source": [
    "#RUN MODEL:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "59a4a7c1",
   "metadata": {},
   "outputs": [],
   "source": [
    "# define model\n",
    "history = model.fit_generator(\n",
    "      train_generator,\n",
    "      steps_per_epoch=6,\n",
    "      epochs=100,\n",
    "      validation_data=validation_generator,\n",
    "      validation_steps=5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d9dc8b61",
   "metadata": {},
   "outputs": [],
   "source": [
    "predictions=model.predict_generator(validation_generator, steps=len(validation_generator), verbose=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 68,
   "id": "ee267bdf",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "val_preds = np.round(predictions)# from probabilities to 0 or 1\n",
    "val_trues = validation_generator.classes"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "06714fcf",
   "metadata": {},
   "outputs": [],
   "source": [
    "#modify:\n",
    "from sklearn.metrics import confusion_matrix\n",
    "from sklearn.metrics import classification_report\n",
    "import numpy as np\n",
    "num_of_test_samples = 160 #number of testing samples\n",
    "batch_size=32 #batch size for the validation set\n",
    "Y_pred = model.predict_generator(validation_generator, num_of_test_samples // batch_size+1)\n",
    "y_pred = np.argmax(Y_pred, axis=1)\n",
    "print('Confusion Matrix')\n",
    "print(confusion_matrix(validation_generator.classes, y_pred))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4a7fa0a5",
   "metadata": {},
   "outputs": [],
   "source": [
    "print('Classification Report')\n",
    "target_names = ['Croc', 'cm']\n",
    "print(classification_report(validation_generator.classes, y_pred, target_names=target_names))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "598b64af",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(train_generator.class_indices)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6f8f01c0",
   "metadata": {
    "collapsed": true,
    "jupyter": {
     "outputs_hidden": true
    }
   },
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "#PLOT\n",
    "acc = history.history['accuracy']\n",
    "val_acc = history.history['val_accuracy']\n",
    "loss = history.history['loss']\n",
    "val_loss = history.history['val_loss']\n",
    "\n",
    "epochs = range(len(acc))\n",
    "\n",
    "plt.plot(epochs, acc, 'bo', label='Training acc')\n",
    "plt.plot(epochs, val_acc, 'b', label='Validation acc')\n",
    "plt.title('Training and validation accuracy')\n",
    "plt.legend()\n",
    "\n",
    "plt.figure()\n",
    "\n",
    "plt.plot(epochs, loss, 'bo', label='Training loss')\n",
    "plt.plot(epochs, val_loss, 'b', label='Validation loss')\n",
    "plt.title('Training and validation loss')\n",
    "plt.legend()\n",
    "\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ad6ed72d",
   "metadata": {
    "collapsed": true,
    "jupyter": {
     "outputs_hidden": true
    }
   },
   "outputs": [],
   "source": [
    "predictions = model.predict_generator(validation_generator, steps=len(validation_generator), verbose=1)\n",
    "import numpy as np\n",
    "from sklearn.metrics import roc_auc_score\n",
    "from tensorflow.keras.utils import to_categorical\n",
    "\n",
    "# True labels\n",
    "val_trues = validation_generator.classes\n",
    "\n",
    "# Ensure predictions are probabilities and not rounded\n",
    "# Convert true labels to one-hot for ROC-AUC calculation\n",
    "val_trues_oh = to_categorical(val_trues, num_classes=2)\n",
    "\n",
    "# Compute ROC-AUC for each class\n",
    "roc_auc = roc_auc_score(val_trues_oh, predictions, average=None)\n",
    "\n",
    "# Print ROC-AUC per class\n",
    "for idx, class_name in enumerate(['Croc', 'cm']):\n",
    "    print(f\"ROC-AUC for class {class_name}: {roc_auc[idx]:.4f}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "34cbf1e7",
   "metadata": {
    "collapsed": true,
    "jupyter": {
     "outputs_hidden": true
    }
   },
   "outputs": [],
   "source": [
    "from sklearn.metrics import roc_auc_score, roc_curve, auc\n",
    "from sklearn.preprocessing import label_binarize\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "n_classes = 2\n",
    "class_names = ['CS', 'SF']\n",
    "\n",
    "# Binarize the test labels\n",
    "test_labels_bin = label_binarize(test_labels, classes=list(range(n_classes)))\n",
    "\n",
    "# Compute ROC curve and ROC area for each class\n",
    "fpr = dict()\n",
    "tpr = dict()\n",
    "roc_auc = dict()\n",
    "for i in range(n_classes):\n",
    "    fpr[i], tpr[i], _ = roc_curve(test_labels_bin[:, i], test_preds[:, i])\n",
    "    roc_auc[i] = auc(fpr[i], tpr[i])\n",
    "\n",
    "# Plot ROC curve for each class\n",
    "plt.figure(figsize=(8, 6))\n",
    "colors = ['green', 'red']\n",
    "for i in range(n_classes):\n",
    "    plt.plot(fpr[i], tpr[i], color=colors[i], lw=2,\n",
    "             label=f'ROC curve for {class_names[i]} (area = {roc_auc[i]:.2f})')\n",
    "\n",
    "plt.plot([0, 1], [0, 1], 'k--', lw=1)\n",
    "plt.xlim([0.0, 1.0])\n",
    "plt.ylim([0.0, 1.05])\n",
    "plt.xlabel('False Positive Rate')\n",
    "plt.ylabel('True Positive Rate')\n",
    "plt.title('Multi-Class ROC Curve')\n",
    "plt.legend(loc='lower right')\n",
    "plt.grid(alpha=0.3)\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6d2d3091",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5bbec4e9",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5d4730ab",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "77f7809c",
   "metadata": {},
   "outputs": [],
   "source": [
    "# STAGE 2: with grayscale intensity augmentation:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "989bbd75",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import tensorflow as tf\n",
    "from tensorflow.keras.applications.resnet50 import preprocess_input\n",
    "import cv2\n",
    "\n",
    "def random_3channel_grayscale_augmentations(image):\n",
    "    # Ensure the input is a 3-channel grayscale image\n",
    "    if len(image.shape) != 3 or image.shape[2] != 3:\n",
    "        raise ValueError(\"Input image must be a 3-channel grayscale image.\")\n",
    "    \n",
    "    # Ensure image values are in the correct range\n",
    "    image = np.clip(image, 0, 255).astype(np.uint8)\n",
    "    \n",
    "    # Process each channel independently\n",
    "    for channel in range(3):\n",
    "        random_value = np.random.randint(0, 3)  # Randomly choose between brightness, contrast, and sharpening\n",
    "        \n",
    "        if random_value == 0:\n",
    "            # Randomly adjust brightness\n",
    "            brightness_scale = np.random.uniform(0.8, 1.2)\n",
    "            image[:, :, channel] = np.clip(image[:, :, channel] * brightness_scale, 0, 255).astype(np.uint8)\n",
    "        elif random_value == 1:\n",
    "            # Randomly adjust contrast\n",
    "            contrast_scale = np.random.uniform(0.8, 1.2)\n",
    "            mean_intensity = np.mean(image[:, :, channel])\n",
    "            image[:, :, channel] = np.clip((image[:, :, channel] - mean_intensity) * contrast_scale + mean_intensity, 0, 255).astype(np.uint8)\n",
    "        else:\n",
    "            # Apply sharpening\n",
    "            kernel = np.array([[0, -1, 0], \n",
    "                               [-1, 5, -1], \n",
    "                               [0, -1, 0]])\n",
    "            image[:, :, channel] = cv2.filter2D(image[:, :, channel], -1, kernel)\n",
    "    \n",
    "    # Convert image to float32 for ResNet50 preprocessing\n",
    "    image = image.astype(np.float32)\n",
    "    \n",
    "    # Apply ResNet50 preprocessing\n",
    "    augmented_image = preprocess_input(image)\n",
    "    \n",
    "    return augmented_image\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "a0782f37",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Found 374 images belonging to 2 classes.\n",
      "Found 160 images belonging to 2 classes.\n"
     ]
    }
   ],
   "source": [
    "#from tensorflow.keras.applications.densenet import preprocess_input #for RESNET 50\n",
    "#from keras.applications.imagenet_utils import decode_predictions, preprocess_input#for efficient Net\n",
    "#from tensorflow.keras.applications.imagenet_utils import decode_predictions, preprocess_input#for efficient Net\n",
    "\n",
    "#Let's train our network using data augmentation and dropout:\n",
    "train_datagen = ImageDataGenerator(\n",
    "    rotation_range=40,\n",
    "    width_shift_range=0.2,\n",
    "    height_shift_range=0.2,\n",
    "    shear_range=0.2,\n",
    "    zoom_range=0.2,\n",
    "    horizontal_flip=True,\n",
    "    preprocessing_function=random_3channel_grayscale_augmentations)\n",
    "\n",
    "# Note that the validation data should not be augmented!\n",
    "test_datagen = ImageDataGenerator(preprocessing_function= \\\n",
    "    keras.applications.resnet50.preprocess_input)#OJO!!!\n",
    "\n",
    "train_generator = train_datagen.flow_from_directory(\n",
    "        # This is the target directory\n",
    "        train_dir,\n",
    "        # All images will be resized \n",
    "        target_size=(80, 400),\n",
    "        batch_size=64,\n",
    "        class_mode='categorical')#put \"categorical\" si son más de 2 categorías\n",
    "\n",
    "validation_generator = test_datagen.flow_from_directory(\n",
    "        validation_dir,\n",
    "        target_size=(80, 400),\n",
    "        batch_size=32,\n",
    "        class_mode='categorical',\n",
    "        shuffle=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "916dfcf9",
   "metadata": {},
   "outputs": [],
   "source": [
    "# for transfer learning RESNET50 with Dropout\n",
    "import sys\n",
    "from matplotlib import pyplot\n",
    "from tensorflow.keras.applications.resnet50 import ResNet50\n",
    "from keras.models import Model\n",
    "from keras.layers import Dense\n",
    "from keras.layers import Flatten\n",
    "from keras.layers import Dropout\n",
    "from keras.optimizers import SGD\n",
    "from keras.optimizers import Adagrad\n",
    "from keras.preprocessing.image import ImageDataGenerator\n",
    "\n",
    "# define cnn model\n",
    "def define_model():\n",
    "\t# load model\n",
    "\tmodel = ResNet50(include_top=False, input_shape=(80, 400, 3))\n",
    "\t# mark loaded layers as not trainable\n",
    "\tfor layer in model.layers:\n",
    "\t\tlayer.trainable = False\n",
    "\t# add new classifier layers\n",
    "\tflat1 = Flatten()(model.layers[-1].output)\n",
    "\tclass1 = Dense(128, activation='relu', kernel_initializer='he_uniform')(flat1)\n",
    "\tdrop=Dropout(0.3)(class1)\n",
    "\toutput = Dense(2, activation='softmax')(drop)\n",
    "\t# define new model\n",
    "\tmodel = Model(inputs=model.inputs, outputs=output)\n",
    "\t# compile model\n",
    "\topt = SGD(lr=0.001, momentum=0.9)\n",
    "\t#opt = Adagrad(lr=0.001)\n",
    "\tmodel.compile(optimizer=opt, loss='categorical_crossentropy', metrics=['accuracy'])\n",
    "\treturn model\n",
    "\n",
    "# define model\n",
    "model = define_model()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "13950337",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "C:\\Users\\IDEA\\Anaconda3\\envs\\gputest\\lib\\site-packages\\tensorflow\\python\\keras\\engine\\training.py:1844: UserWarning: `Model.fit_generator` is deprecated and will be removed in a future version. Please use `Model.fit`, which supports generators.\n",
      "  warnings.warn('`Model.fit_generator` is deprecated and '\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 1/100\n",
      "6/6 [==============================] - 54s 2s/step - loss: 2.4414 - accuracy: 0.7864 - val_loss: 0.2236 - val_accuracy: 0.9187\n",
      "Epoch 2/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.2256 - accuracy: 0.9215 - val_loss: 0.1714 - val_accuracy: 0.9250\n",
      "Epoch 3/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.2291 - accuracy: 0.9423 - val_loss: 0.1050 - val_accuracy: 0.9563\n",
      "Epoch 4/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0645 - accuracy: 0.9773 - val_loss: 0.0658 - val_accuracy: 0.9750\n",
      "Epoch 5/100\n",
      "6/6 [==============================] - 7s 1s/step - loss: 0.0786 - accuracy: 0.9757 - val_loss: 0.0655 - val_accuracy: 0.9750\n",
      "Epoch 6/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0304 - accuracy: 0.9906 - val_loss: 0.0568 - val_accuracy: 0.9750\n",
      "Epoch 7/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0765 - accuracy: 0.9696 - val_loss: 0.0557 - val_accuracy: 0.9750\n",
      "Epoch 8/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0344 - accuracy: 0.9908 - val_loss: 0.0461 - val_accuracy: 0.9812\n",
      "Epoch 9/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0143 - accuracy: 0.9980 - val_loss: 0.0453 - val_accuracy: 0.9812\n",
      "Epoch 10/100\n",
      "6/6 [==============================] - 7s 1s/step - loss: 0.0327 - accuracy: 0.9905 - val_loss: 0.0421 - val_accuracy: 0.9812\n",
      "Epoch 11/100\n",
      "6/6 [==============================] - 7s 1s/step - loss: 0.0539 - accuracy: 0.9841 - val_loss: 0.0354 - val_accuracy: 0.9875\n",
      "Epoch 12/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0636 - accuracy: 0.9832 - val_loss: 0.0288 - val_accuracy: 0.9875\n",
      "Epoch 13/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0399 - accuracy: 0.9832 - val_loss: 0.0401 - val_accuracy: 0.9812\n",
      "Epoch 14/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0193 - accuracy: 0.9950 - val_loss: 0.0539 - val_accuracy: 0.9812\n",
      "Epoch 15/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0169 - accuracy: 0.9944 - val_loss: 0.0502 - val_accuracy: 0.9812\n",
      "Epoch 16/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0463 - accuracy: 0.9869 - val_loss: 0.0382 - val_accuracy: 0.9812\n",
      "Epoch 17/100\n",
      "6/6 [==============================] - 7s 1s/step - loss: 0.0313 - accuracy: 0.9892 - val_loss: 0.0357 - val_accuracy: 0.9812\n",
      "Epoch 18/100\n",
      "6/6 [==============================] - 7s 1s/step - loss: 0.0185 - accuracy: 0.9962 - val_loss: 0.0299 - val_accuracy: 0.9812\n",
      "Epoch 19/100\n",
      "6/6 [==============================] - 7s 1s/step - loss: 0.0197 - accuracy: 0.9954 - val_loss: 0.0331 - val_accuracy: 0.9812\n",
      "Epoch 20/100\n",
      "6/6 [==============================] - 7s 1s/step - loss: 0.0344 - accuracy: 0.9851 - val_loss: 0.0324 - val_accuracy: 0.9812\n",
      "Epoch 21/100\n",
      "6/6 [==============================] - 7s 1s/step - loss: 0.0434 - accuracy: 0.9803 - val_loss: 0.0305 - val_accuracy: 0.9812\n",
      "Epoch 22/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0464 - accuracy: 0.9921 - val_loss: 0.0296 - val_accuracy: 0.9812\n",
      "Epoch 23/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0466 - accuracy: 0.9889 - val_loss: 0.0360 - val_accuracy: 0.9812\n",
      "Epoch 24/100\n",
      "6/6 [==============================] - 7s 1s/step - loss: 0.0165 - accuracy: 0.9941 - val_loss: 0.0472 - val_accuracy: 0.9812\n",
      "Epoch 25/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0171 - accuracy: 0.9949 - val_loss: 0.0474 - val_accuracy: 0.9812\n",
      "Epoch 26/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0144 - accuracy: 0.9992 - val_loss: 0.0431 - val_accuracy: 0.9812\n",
      "Epoch 27/100\n",
      "6/6 [==============================] - 7s 1s/step - loss: 0.0309 - accuracy: 0.9887 - val_loss: 0.0293 - val_accuracy: 0.9812\n",
      "Epoch 28/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0354 - accuracy: 0.9862 - val_loss: 0.0258 - val_accuracy: 0.9875\n",
      "Epoch 29/100\n",
      "6/6 [==============================] - 7s 1s/step - loss: 0.0132 - accuracy: 0.9938 - val_loss: 0.0265 - val_accuracy: 0.9875\n",
      "Epoch 30/100\n",
      "6/6 [==============================] - 7s 1s/step - loss: 0.0420 - accuracy: 0.9896 - val_loss: 0.0220 - val_accuracy: 0.9875\n",
      "Epoch 31/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0146 - accuracy: 0.9945 - val_loss: 0.0234 - val_accuracy: 0.9875\n",
      "Epoch 32/100\n",
      "6/6 [==============================] - 7s 1s/step - loss: 0.0106 - accuracy: 0.9976 - val_loss: 0.0233 - val_accuracy: 0.9875\n",
      "Epoch 33/100\n",
      "6/6 [==============================] - 7s 1s/step - loss: 0.0235 - accuracy: 0.9954 - val_loss: 0.0250 - val_accuracy: 0.9875\n",
      "Epoch 34/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0108 - accuracy: 1.0000 - val_loss: 0.0265 - val_accuracy: 0.9812\n",
      "Epoch 35/100\n",
      "6/6 [==============================] - 7s 1s/step - loss: 0.0135 - accuracy: 0.9970 - val_loss: 0.0297 - val_accuracy: 0.9812\n",
      "Epoch 36/100\n",
      "6/6 [==============================] - 7s 1s/step - loss: 0.0142 - accuracy: 0.9974 - val_loss: 0.0319 - val_accuracy: 0.9812\n",
      "Epoch 37/100\n",
      "6/6 [==============================] - 7s 1s/step - loss: 0.0053 - accuracy: 1.0000 - val_loss: 0.0318 - val_accuracy: 0.9812\n",
      "Epoch 38/100\n",
      "6/6 [==============================] - 7s 1s/step - loss: 0.0048 - accuracy: 0.9982 - val_loss: 0.0319 - val_accuracy: 0.9812\n",
      "Epoch 39/100\n",
      "6/6 [==============================] - 7s 1s/step - loss: 0.0436 - accuracy: 0.9859 - val_loss: 0.0275 - val_accuracy: 0.9875\n",
      "Epoch 40/100\n",
      "6/6 [==============================] - 7s 1s/step - loss: 0.0200 - accuracy: 0.9910 - val_loss: 0.0254 - val_accuracy: 0.9875\n",
      "Epoch 41/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0194 - accuracy: 0.9923 - val_loss: 0.0229 - val_accuracy: 0.9875\n",
      "Epoch 42/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0067 - accuracy: 0.9992 - val_loss: 0.0244 - val_accuracy: 0.9875\n",
      "Epoch 43/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0070 - accuracy: 1.0000 - val_loss: 0.0280 - val_accuracy: 0.9875\n",
      "Epoch 44/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0461 - accuracy: 0.9856 - val_loss: 0.0294 - val_accuracy: 0.9875\n",
      "Epoch 45/100\n",
      "6/6 [==============================] - 7s 1s/step - loss: 0.0166 - accuracy: 0.9909 - val_loss: 0.0233 - val_accuracy: 0.9875\n",
      "Epoch 46/100\n",
      "6/6 [==============================] - 7s 1s/step - loss: 0.0162 - accuracy: 0.9944 - val_loss: 0.0201 - val_accuracy: 0.9875\n",
      "Epoch 47/100\n",
      "6/6 [==============================] - 7s 1s/step - loss: 0.0294 - accuracy: 0.9792 - val_loss: 0.0236 - val_accuracy: 0.9875\n",
      "Epoch 48/100\n",
      "6/6 [==============================] - 7s 1s/step - loss: 0.0065 - accuracy: 0.9988 - val_loss: 0.0273 - val_accuracy: 0.9875\n",
      "Epoch 49/100\n",
      "6/6 [==============================] - 7s 1s/step - loss: 0.0245 - accuracy: 0.9897 - val_loss: 0.0238 - val_accuracy: 0.9875\n",
      "Epoch 50/100\n",
      "6/6 [==============================] - 7s 1s/step - loss: 0.0117 - accuracy: 0.9945 - val_loss: 0.0213 - val_accuracy: 0.9875\n",
      "Epoch 51/100\n",
      "6/6 [==============================] - 7s 1s/step - loss: 0.0135 - accuracy: 0.9974 - val_loss: 0.0200 - val_accuracy: 0.9875\n",
      "Epoch 52/100\n",
      "6/6 [==============================] - 7s 1s/step - loss: 0.0086 - accuracy: 0.9982 - val_loss: 0.0186 - val_accuracy: 0.9875\n",
      "Epoch 53/100\n",
      "6/6 [==============================] - 7s 1s/step - loss: 0.0105 - accuracy: 0.9974 - val_loss: 0.0157 - val_accuracy: 0.9875\n",
      "Epoch 54/100\n",
      "6/6 [==============================] - 7s 1s/step - loss: 0.0074 - accuracy: 1.0000 - val_loss: 0.0139 - val_accuracy: 0.9937\n",
      "Epoch 55/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0100 - accuracy: 0.9982 - val_loss: 0.0143 - val_accuracy: 0.9937\n",
      "Epoch 56/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0153 - accuracy: 0.9910 - val_loss: 0.0135 - val_accuracy: 0.9937\n",
      "Epoch 57/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0074 - accuracy: 0.9988 - val_loss: 0.0124 - val_accuracy: 0.9937\n",
      "Epoch 58/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0366 - accuracy: 0.9951 - val_loss: 0.0103 - val_accuracy: 0.9937\n",
      "Epoch 59/100\n",
      "6/6 [==============================] - 7s 1s/step - loss: 0.0093 - accuracy: 0.9967 - val_loss: 0.0108 - val_accuracy: 0.9937\n",
      "Epoch 60/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0104 - accuracy: 0.9982 - val_loss: 0.0139 - val_accuracy: 0.9937\n",
      "Epoch 61/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0071 - accuracy: 0.9982 - val_loss: 0.0157 - val_accuracy: 0.9937\n",
      "Epoch 62/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0181 - accuracy: 0.9945 - val_loss: 0.0144 - val_accuracy: 0.9937\n",
      "Epoch 63/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0078 - accuracy: 0.9974 - val_loss: 0.0131 - val_accuracy: 0.9937\n",
      "Epoch 64/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0129 - accuracy: 0.9954 - val_loss: 0.0121 - val_accuracy: 0.9937\n",
      "Epoch 65/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0272 - accuracy: 0.9853 - val_loss: 0.0136 - val_accuracy: 0.9937\n",
      "Epoch 66/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0041 - accuracy: 0.9988 - val_loss: 0.0171 - val_accuracy: 0.9875\n",
      "Epoch 67/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0461 - accuracy: 0.9865 - val_loss: 0.0146 - val_accuracy: 0.9937\n",
      "Epoch 68/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0065 - accuracy: 1.0000 - val_loss: 0.0126 - val_accuracy: 0.9937\n",
      "Epoch 69/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0174 - accuracy: 0.9922 - val_loss: 0.0110 - val_accuracy: 0.9937\n",
      "Epoch 70/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0142 - accuracy: 0.9957 - val_loss: 0.0090 - val_accuracy: 1.0000\n",
      "Epoch 71/100\n",
      "6/6 [==============================] - 7s 1s/step - loss: 0.0070 - accuracy: 0.9966 - val_loss: 0.0083 - val_accuracy: 1.0000\n",
      "Epoch 72/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0172 - accuracy: 0.9975 - val_loss: 0.0085 - val_accuracy: 1.0000\n",
      "Epoch 73/100\n",
      "6/6 [==============================] - 7s 1s/step - loss: 0.0099 - accuracy: 0.9980 - val_loss: 0.0096 - val_accuracy: 0.9937\n",
      "Epoch 74/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0085 - accuracy: 0.9980 - val_loss: 0.0120 - val_accuracy: 0.9937\n",
      "Epoch 75/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0155 - accuracy: 0.9939 - val_loss: 0.0130 - val_accuracy: 0.9937\n",
      "Epoch 76/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0060 - accuracy: 1.0000 - val_loss: 0.0140 - val_accuracy: 0.9937\n",
      "Epoch 77/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0077 - accuracy: 0.9970 - val_loss: 0.0152 - val_accuracy: 0.9937\n",
      "Epoch 78/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0078 - accuracy: 0.9962 - val_loss: 0.0157 - val_accuracy: 0.9937\n",
      "Epoch 79/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0047 - accuracy: 1.0000 - val_loss: 0.0171 - val_accuracy: 0.9875\n",
      "Epoch 80/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0309 - accuracy: 0.9891 - val_loss: 0.0152 - val_accuracy: 0.9937\n",
      "Epoch 81/100\n",
      "6/6 [==============================] - 11s 2s/step - loss: 0.0026 - accuracy: 1.0000 - val_loss: 0.0146 - val_accuracy: 0.9937\n",
      "Epoch 82/100\n",
      "6/6 [==============================] - 9s 1s/step - loss: 0.0042 - accuracy: 1.0000 - val_loss: 0.0138 - val_accuracy: 0.9937\n",
      "Epoch 83/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0060 - accuracy: 0.9992 - val_loss: 0.0130 - val_accuracy: 0.9937\n",
      "Epoch 84/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0022 - accuracy: 1.0000 - val_loss: 0.0133 - val_accuracy: 0.9937\n",
      "Epoch 85/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0026 - accuracy: 1.0000 - val_loss: 0.0138 - val_accuracy: 0.9937\n",
      "Epoch 86/100\n",
      "6/6 [==============================] - 7s 1s/step - loss: 0.0199 - accuracy: 0.9925 - val_loss: 0.0174 - val_accuracy: 0.9937\n",
      "Epoch 87/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0030 - accuracy: 1.0000 - val_loss: 0.0193 - val_accuracy: 0.9875\n",
      "Epoch 88/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0198 - accuracy: 0.9931 - val_loss: 0.0168 - val_accuracy: 0.9937\n",
      "Epoch 89/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0112 - accuracy: 0.9923 - val_loss: 0.0131 - val_accuracy: 0.9937\n",
      "Epoch 90/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0136 - accuracy: 0.9938 - val_loss: 0.0125 - val_accuracy: 0.9937\n",
      "Epoch 91/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0129 - accuracy: 0.9929 - val_loss: 0.0145 - val_accuracy: 0.9937\n",
      "Epoch 92/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0176 - accuracy: 0.9882 - val_loss: 0.0179 - val_accuracy: 0.9937\n",
      "Epoch 93/100\n",
      "6/6 [==============================] - 6s 1s/step - loss: 0.0040 - accuracy: 0.9988 - val_loss: 0.0198 - val_accuracy: 0.9875\n",
      "Epoch 94/100\n",
      "6/6 [==============================] - 8s 1s/step - loss: 0.0123 - accuracy: 0.9924 - val_loss: 0.0174 - val_accuracy: 0.9937\n",
      "Epoch 95/100\n",
      "6/6 [==============================] - 7s 1s/step - loss: 0.0035 - accuracy: 0.9992 - val_loss: 0.0154 - val_accuracy: 0.9937\n",
      "Epoch 96/100\n",
      "6/6 [==============================] - 7s 1s/step - loss: 0.0015 - accuracy: 1.0000 - val_loss: 0.0159 - val_accuracy: 0.9937\n",
      "Epoch 97/100\n",
      "6/6 [==============================] - 7s 1s/step - loss: 0.0046 - accuracy: 1.0000 - val_loss: 0.0176 - val_accuracy: 0.9937\n",
      "Epoch 98/100\n",
      "6/6 [==============================] - 7s 1s/step - loss: 0.0684 - accuracy: 0.9827 - val_loss: 0.0167 - val_accuracy: 0.9937\n",
      "Epoch 99/100\n",
      "6/6 [==============================] - 7s 1s/step - loss: 0.0172 - accuracy: 0.9932 - val_loss: 0.0196 - val_accuracy: 0.9937\n",
      "Epoch 100/100\n",
      "6/6 [==============================] - 7s 1s/step - loss: 0.0101 - accuracy: 0.9882 - val_loss: 0.0214 - val_accuracy: 0.9875\n"
     ]
    }
   ],
   "source": [
    "# define model\n",
    "history = model.fit_generator(\n",
    "      train_generator,\n",
    "      steps_per_epoch=6,\n",
    "      epochs=100,\n",
    "      validation_data=validation_generator,\n",
    "      validation_steps=5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "7e6bf47d",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "C:\\Users\\IDEA\\Anaconda3\\envs\\gputest\\lib\\site-packages\\tensorflow\\python\\keras\\engine\\training.py:1905: UserWarning: `Model.predict_generator` is deprecated and will be removed in a future version. Please use `Model.predict`, which supports generators.\n",
      "  warnings.warn('`Model.predict_generator` is deprecated and '\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "5/5 [==============================] - 2s 141ms/step\n"
     ]
    }
   ],
   "source": [
    "predictions=model.predict_generator(validation_generator, steps=len(validation_generator), verbose=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1eed5d22-f76f-4add-b20c-215121c3449d",
   "metadata": {},
   "outputs": [],
   "source": [
    "predictions=model.predict_generator(validation_generator, steps=len(validation_generator), verbose=1)\n",
    "import numpy as np\n",
    "val_preds = np.round(predictions)# from probabilities to 0 or 1\n",
    "val_trues = validation_generator.classes"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "7fa27e5f",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "C:\\Users\\IDEA\\Anaconda3\\envs\\gputest\\lib\\site-packages\\tensorflow\\python\\keras\\engine\\training.py:1905: UserWarning: `Model.predict_generator` is deprecated and will be removed in a future version. Please use `Model.predict`, which supports generators.\n",
      "  warnings.warn('`Model.predict_generator` is deprecated and '\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:Your input ran out of data; interrupting training. Make sure that your dataset or generator can generate at least `steps_per_epoch * epochs` batches (in this case, 6 batches). You may need to use the repeat() function when building your dataset.\n",
      "Confusion Matrix\n",
      "[[ 12   2]\n",
      " [  0 146]]\n"
     ]
    }
   ],
   "source": [
    "#modify:\n",
    "from sklearn.metrics import confusion_matrix\n",
    "from sklearn.metrics import classification_report\n",
    "import numpy as np\n",
    "num_of_test_samples = 160 #number of testing samples\n",
    "batch_size=32 #batch size for the validation set\n",
    "Y_pred = model.predict_generator(validation_generator, num_of_test_samples // batch_size+1)\n",
    "y_pred = np.argmax(Y_pred, axis=1)\n",
    "print('Confusion Matrix')\n",
    "print(confusion_matrix(validation_generator.classes, y_pred))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "da3a01c3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Classification Report\n",
      "              precision    recall  f1-score   support\n",
      "\n",
      "        Croc       1.00      0.86      0.92        14\n",
      "          cm       0.99      1.00      0.99       146\n",
      "\n",
      "    accuracy                           0.99       160\n",
      "   macro avg       0.99      0.93      0.96       160\n",
      "weighted avg       0.99      0.99      0.99       160\n",
      "\n"
     ]
    }
   ],
   "source": [
    "print('Classification Report')\n",
    "target_names = ['Croc', 'cm']\n",
    "print(classification_report(validation_generator.classes, y_pred, target_names=target_names))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4f769f19-3a2d-4d16-a1ca-13c4a05e975f",
   "metadata": {},
   "outputs": [],
   "source": [
    "predictions = model.predict_generator(validation_generator, steps=len(validation_generator), verbose=1)\n",
    "import numpy as np\n",
    "from sklearn.metrics import roc_auc_score\n",
    "from tensorflow.keras.utils import to_categorical\n",
    "\n",
    "# True labels\n",
    "val_trues = validation_generator.classes\n",
    "\n",
    "# Ensure predictions are probabilities and not rounded\n",
    "# Convert true labels to one-hot for ROC-AUC calculation\n",
    "val_trues_oh = to_categorical(val_trues, num_classes=2)\n",
    "\n",
    "# Compute ROC-AUC for each class\n",
    "roc_auc = roc_auc_score(val_trues_oh, predictions, average=None)\n",
    "\n",
    "# Print ROC-AUC per class\n",
    "for idx, class_name in enumerate(['Croc', 'cm']):\n",
    "    print(f\"ROC-AUC for class {class_name}: {roc_auc[idx]:.4f}\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b9f17577-870d-4131-8447-868c761a0ffd",
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.metrics import roc_auc_score, roc_curve, auc\n",
    "from sklearn.preprocessing import label_binarize\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "n_classes = 2\n",
    "class_names = ['CS', 'SF']\n",
    "\n",
    "# Binarize the test labels\n",
    "test_labels_bin = label_binarize(test_labels, classes=list(range(n_classes)))\n",
    "\n",
    "# Compute ROC curve and ROC area for each class\n",
    "fpr = dict()\n",
    "tpr = dict()\n",
    "roc_auc = dict()\n",
    "for i in range(n_classes):\n",
    "    fpr[i], tpr[i], _ = roc_curve(test_labels_bin[:, i], test_preds[:, i])\n",
    "    roc_auc[i] = auc(fpr[i], tpr[i])\n",
    "\n",
    "# Plot ROC curve for each class\n",
    "plt.figure(figsize=(8, 6))\n",
    "colors = ['green', 'red']\n",
    "for i in range(n_classes):\n",
    "    plt.plot(fpr[i], tpr[i], color=colors[i], lw=2,\n",
    "             label=f'ROC curve for {class_names[i]} (area = {roc_auc[i]:.2f})')\n",
    "\n",
    "plt.plot([0, 1], [0, 1], 'k--', lw=1)\n",
    "plt.xlim([0.0, 1.0])\n",
    "plt.ylim([0.0, 1.05])\n",
    "plt.xlabel('False Positive Rate')\n",
    "plt.ylabel('True Positive Rate')\n",
    "plt.title('Multi-Class ROC Curve')\n",
    "plt.legend(loc='lower right')\n",
    "plt.grid(alpha=0.3)\n",
    "plt.tight_layout()\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "9957c9da",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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KIF+DtBPUl9J7Qgupj6ZlGYFcHJYiIOATwNvh/OPl1QZ9t+fDLyZcymdc87lLWugN7MG4FLgWYIg/YiZLZR0kaSLwmQ3A8MkvQ7MAn5oZh+Y2SvAY8Bc4PVwe1NKUA/nXDOZNg2qq6FNm+DvtGn557V27VoGDx7M4MGDOfjgg+nZs+eu6e3bt2dct66ujssvvzzrNoYNK79OBZkld7c3v5qaGqurq2vuYjhXlt58800GDBgQKe20aTB+PGzevHteZSVMmQJjxxZWjhtvvJFOnTpx5ZVX7prX0NBA27ZRxpDEW6pjJOlVM6tJld5/Geucy9vEiXsGeQimJ04s3jbOP/98/u3f/o0vfOEL/PCHP2T27NkMGzaMY489lmHDhrFkyRIAXnjhBc444wwg+JC44IILGD58OIcddhh33nnnrvw6deq0K/3w4cMZPXo0/fv3Z+zYsTQ2fGfOnEn//v056aSTuPzyy3flm2jZsmV89rOfZciQIQwZMoS//e1vu5bddtttHH300QwaNIirrw5+Y7p06VJOOeUUBg0axJAhQ/jHP/5RvJ2UhX80OufytmJFbvPz9dZbb/H8889TUVHBxo0befHFF2nbti3PP/881157Lb/5zW/2Wmfx4sX86U9/YtOmTXz6059mwoQJe409f+2111i4cCGHHnooJ554Ii+99BI1NTVceOGFvPjii/Tt25fa2tqUZTrwwAN57rnn6NChA3//+9+pra2lrq6Op59+mieeeIJXXnmFyspK1q1bB8DYsWO5+uqrOfvss9m6dSs7d+4s7k7KwAO9cy5vvXvD8uWp5xfT1772NSoqKgDYsGED5513Hn//+9+RxI4dO1KuM3LkSNq3b0/79u058MADWb16NVVVVXukGTp06K55gwcPZtmyZXTq1InDDjts1zj12tpapkzZ+9Lijh07uPTSS5k3bx4VFRW89dZbADz//POMGzeOyspKAA444AA2bdrEu+++y9lnnw0EP3pqSt5145zL26RJQZ98osrKYH4x7bvvvrveX3/99XzhC1/gjTfe4Kmnnko7prx9+/a73ldUVNDQ0BApTdTrlj//+c856KCDmD9/PnV1dbsuFpvZXkMgm/taqAd651zexo4NLrz26QNS8LcYF2Iz2bBhAz179gTgvvvuK3r+/fv35+2332bZsmUAPPzww2nLccghh9CmTRsefPBBPvnkEwC+9KUvMXXqVDaHFy/WrVvHfvvtR1VVFU888QQA27Zt27W8KXigd84VZOxYWLYMdu4M/pYyyANcddVVXHPNNZx44om7gmsxdezYkV/+8peMGDGCk046iYMOOoj9999/r3QXX3wx999/P8cffzxvvfXWrm8dI0aM4Mwzz6SmpobBgwdz++23A/Dggw9y5513cswxxzBs2DDef//9opc9HR9e6ZzbQy7DK+Pqo48+olOnTpgZl1xyCf369eOKK65o7mLt4sMrnXOuQP/93//N4MGDOfLII9mwYQMXXnhhcxepID7qxjnnklxxxRUtqgVfKG/RO+dczHmgd865mPNA75xzMeeB3jnnYs4DvXOuxRg+fDjPPvvsHvPuuOMOLr744ozrNA7H/vKXv8yHH364V5obb7xx13j2dJ544gkWLdr9KOz/+I//4Pnnn8+h9C2XB3rnXItRW1vLjBkz9pg3Y8aMtDcWSzZz5ky6dOmS17aTA/1NN93EKaeckldeLY0Pr3TOpfX978O8ecXNc/BguOOO1MtGjx7Nddddx7Zt22jfvj3Lli3jvffe46STTmLChAnMmTOHLVu2MHr0aH70ox/ttX51dTV1dXV0796dSZMm8cADD9CrVy969OjBcccdBwRj5KdMmcL27dv51Kc+xYMPPsi8efN48skn+fOf/8wtt9zCb37zG26++WbOOOMMRo8ezaxZs7jyyitpaGjgX/7lX7jnnnto37491dXVnHfeeTz11FPs2LGDRx99lP79++9RpmXLlvHNb36Tjz/+GIC77rpr18NPbrvtNh588EHatGnD6aefzq233srSpUu56KKLWLNmDRUVFTz66KMcfvjhBe1zb9E751qMbt26MXToUJ555hkgaM2fe+65SGLSpEnU1dWxYMEC/vznP7NgwYK0+bz66qvMmDGD1157jccff5w5c+bsWnbOOecwZ84c5s+fz4ABA7j33nsZNmwYZ555Jj/5yU+YN2/eHoF169atnH/++Tz88MO8/vrrNDQ0cM899+xa3r17d+bOncuECRNSdg813s547ty5PPzww7uegpV4O+P58+dz1VVXAcHtjC+55BLmz5/P3/72Nw455JDCdireonfOZZCu5V1Kjd03o0aNYsaMGUydOhWARx55hClTptDQ0MCqVatYtGgRxxxzTMo8/vKXv3D22Wfv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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "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inxtXkSkWTKz+P8QrqMhFxGRiFCgi4hEhAJdRCQidB26SAty6NAhqqqqOHjwYPrCklcFBQUUFxfTpk2bjNdRoIu0IFVVVXTs2JHS0lKSfz+J5Ju7U11dTVVVFb169cp4vWY15LJ8OZSWwkknBT+X6+tyRbJy8OBBioqKFOYnODOjqKgo609SzeYIfflymD0b9odfjVBZGTwHmDYtf+0SaW4U5s1Dfd6nZnOEPn/+0TCvtX9/MF9ERJpRoL//fnbzReTEU11dTVlZGWVlZZxxxhn06NGj7vnnn3+ect2KigpuuOGGtNsYOXJkTtq6du1aJk6cmJO6mkqzCfSe8V/elWa+iDRcrs9bFRUVsXHjRjZu3MicOXO4+eab6563bduWw4cPJ123vLycpUuXpt3GK6+80rBGNmPNJtAXLYLCwmPnFRYG80Uk92rPW1VWgvvR81a5vhhh5syZzJs3j4suuojbbruN1157jZEjRzJ48GBGjhzJtm3bgGOPmBcuXMisWbMYO3YsvXv3PiboO3ToUFd+7NixTJkyhb59+zJt2jRq7y67evVq+vbty+jRo7nhhhvSHol/+umnXH755QwcOJARI0awadMmAH7zm9/UfcIYPHgwNTU17Ny5kzFjxlBWVsa5557LSy+9lNsXLIVmc1K09sTn/PnBMEvPnkGY64SoSONIdd4q1393f/rTn1izZg2tWrVi7969rFu3jtatW7NmzRruvPNOVq5cedw6W7du5cUXX6Smpoazzz6buXPnHnfN9htvvMGWLVvo3r07o0aN4re//S3l5eVcd911rFu3jl69ejF16tS07VuwYAGDBw9m1apVvPDCC8yYMYONGzeyePFi7rnnHkaNGsW+ffsoKChg2bJlfOlLX2L+/PkcOXKE/fEvYiNqNoEOwS+RAlykaTTleasrrriCVq1aAbBnzx6uvvpq3n77bcyMQ4cOJVzn0ksvpV27drRr146uXbvy8ccfU1xcfEyZ4cOH180rKytjx44ddOjQgd69e9dd3z116lSWLVuWsn0vv/xy3U7li1/8ItXV1ezZs4dRo0Yxb948pk2bxuTJkykuLmbYsGHMmjWLQ4cOcfnll1NWVtaQlyYraYdczOxMM3vRzN4ysy1mdmOCMmZmS81su5ltMrMhjdNcEWkqTXne6uSTT657fNddd3HRRRexefNmnn766aTX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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "#PLOT\n",
    "acc = history.history['accuracy']\n",
    "val_acc = history.history['val_accuracy']\n",
    "loss = history.history['loss']\n",
    "val_loss = history.history['val_loss']\n",
    "\n",
    "epochs = range(len(acc))\n",
    "\n",
    "plt.plot(epochs, acc, 'bo', label='Training acc')\n",
    "plt.plot(epochs, val_acc, 'b', label='Validation acc')\n",
    "plt.title('Training and validation accuracy')\n",
    "plt.legend()\n",
    "\n",
    "plt.figure()\n",
    "\n",
    "plt.plot(epochs, loss, 'bo', label='Training loss')\n",
    "plt.plot(epochs, val_loss, 'b', label='Validation loss')\n",
    "plt.title('Training and validation loss')\n",
    "plt.legend()\n",
    "\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ddfd3320",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "471343ea",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "53b13373",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6fbcd54d",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "40b275f2",
   "metadata": {},
   "outputs": [],
   "source": [
    "#STAGE 3: With cross-validation and non color-augmentation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "37180e35",
   "metadata": {},
   "outputs": [],
   "source": [
    "#WITH STRATIFIED K-FOLD FOR IMBALANCED SETS AND CLASSIFICATION REPORT. This is the best\n",
    "\n",
    "import os\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "from sklearn.model_selection import StratifiedKFold\n",
    "from sklearn.utils import class_weight\n",
    "from tensorflow.keras.preprocessing.image import ImageDataGenerator\n",
    "from tensorflow.keras.applications.resnet50 import preprocess_input\n",
    "from keras.models import Model\n",
    "from keras.layers import Dense, Flatten, Dropout\n",
    "from keras.optimizers import SGD\n",
    "from keras.applications.resnet50 import ResNet50\n",
    "from sklearn.metrics import classification_report\n",
    "\n",
    "original_dir2 = os.path.abspath('C:\\\\Users\\\\oscar\\\\Documents\\\\experimentos\\\\cocos\\\\kfold')\n",
    "# Paths to the directories\n",
    "train_croc_dir = os.path.join(original_dir2, 'CS1')\n",
    "train_cm_dir = os.path.join(original_dir2, 'SF')\n",
    "\n",
    "# Collect all image paths and corresponding labels\n",
    "all_image_paths = []\n",
    "all_labels = []\n",
    "\n",
    "for class_name, class_dir in [('CS1', train_croc_dir), ('SF', train_cm_dir)]:\n",
    "    for image_file in os.listdir(class_dir):\n",
    "        all_image_paths.append(os.path.join(class_dir, image_file))\n",
    "        all_labels.append(0 if class_name == 'CS1' else 1)\n",
    "\n",
    "# Convert to NumPy arrays\n",
    "all_image_paths = np.array(all_image_paths)\n",
    "all_labels = np.array(all_labels)\n",
    "\n",
    "# K-Fold Cross-Validation (Stratified)\n",
    "skf = StratifiedKFold(n_splits=4, shuffle=True, random_state=42)# change to nsplits=5 for a 80-20 split\n",
    "\n",
    "# Data generators\n",
    "datagen = ImageDataGenerator(preprocessing_function=preprocess_input)\n",
    "\n",
    "# Define the model\n",
    "def define_model():\n",
    "    # Load ResNet50 without the top layer\n",
    "    model = ResNet50(include_top=False, input_shape=(80, 400, 3))\n",
    "    \n",
    "    # Mark the layers of ResNet50 as not trainable\n",
    "    for layer in model.layers:\n",
    "        layer.trainable = False\n",
    "    \n",
    "    # Add custom layers for classification\n",
    "    flat1 = Flatten()(model.layers[-1].output)\n",
    "    class1 = Dense(128, activation='relu', kernel_initializer='he_uniform')(flat1)\n",
    "    drop = Dropout(0.3)(class1)\n",
    "    output = Dense(2, activation='softmax')(drop)\n",
    "    \n",
    "    # Define the new model\n",
    "    model = Model(inputs=model.inputs, outputs=output)\n",
    "    \n",
    "    # Compile the model with SGD optimizer\n",
    "    opt = SGD(lr=0.001, momentum=0.9)\n",
    "    model.compile(optimizer=opt, loss='categorical_crossentropy', metrics=['accuracy'])\n",
    "    \n",
    "    return model\n",
    "\n",
    "# Initialize a list to store accuracies and true/false labels for classification report\n",
    "fold_accuracies = []\n",
    "all_true_labels = []\n",
    "all_pred_labels = []\n",
    "\n",
    "# Loop over each fold\n",
    "for fold, (train_idx, val_idx) in enumerate(skf.split(all_image_paths, all_labels)):\n",
    "    print(f\"Training Fold {fold + 1}/4...\")\n",
    "    \n",
    "    # Split data into training and validation sets\n",
    "    train_images = all_image_paths[train_idx]\n",
    "    train_labels = all_labels[train_idx]\n",
    "    val_images = all_image_paths[val_idx]\n",
    "    val_labels = all_labels[val_idx]\n",
    "    \n",
    "    # Calculate class weights to handle class imbalance (using integer labels)\n",
    "    class_weights = class_weight.compute_class_weight('balanced', classes=np.unique(all_labels), y=train_labels)\n",
    "    class_weight_dict = dict(enumerate(class_weights))\n",
    "    \n",
    "    # Convert labels to strings (for flow_from_dataframe compatibility)\n",
    "    train_labels_str = train_labels.astype(str)\n",
    "    val_labels_str = val_labels.astype(str)\n",
    "\n",
    "    # Create training generator\n",
    "    train_generator = datagen.flow_from_dataframe(\n",
    "        dataframe=pd.DataFrame({'filename': train_images, 'class': train_labels_str}),\n",
    "        x_col='filename',\n",
    "        y_col='class',\n",
    "        target_size=(80, 400),\n",
    "        batch_size=64,\n",
    "        class_mode='categorical'\n",
    "    )\n",
    "    \n",
    "    # Create validation generator\n",
    "    validation_generator = datagen.flow_from_dataframe(\n",
    "        dataframe=pd.DataFrame({'filename': val_images, 'class': val_labels_str}),\n",
    "        x_col='filename',\n",
    "        y_col='class',\n",
    "        target_size=(80, 400),\n",
    "        batch_size=32,\n",
    "        class_mode='categorical',\n",
    "        shuffle=False\n",
    "    )\n",
    "    \n",
    "    # Define the model for this fold\n",
    "    model = define_model()\n",
    "\n",
    "    # Calculate steps per epoch\n",
    "    steps_per_epoch = np.ceil(len(train_images) / 64)\n",
    "    validation_steps = np.ceil(len(val_images) / 32)\n",
    "    \n",
    "    # Train the model\n",
    "    history = model.fit(\n",
    "        train_generator,\n",
    "        steps_per_epoch=steps_per_epoch,\n",
    "        epochs=10,\n",
    "        validation_data=validation_generator,\n",
    "        validation_steps=validation_steps,\n",
    "        class_weight=class_weight_dict,  # Use class weights to handle class imbalance\n",
    "        verbose=1\n",
    "    )\n",
    "    \n",
    "    # Evaluate the model\n",
    "    val_loss, val_accuracy = model.evaluate(validation_generator, verbose=0)\n",
    "    fold_accuracies.append(val_accuracy)\n",
    "    print(f\"Fold {fold + 1} accuracy: {val_accuracy:.4f}\")\n",
    "    \n",
    "    # Get the true and predicted labels for classification report\n",
    "    val_labels_true = val_labels.astype(int)\n",
    "    val_labels_pred = model.predict(validation_generator)\n",
    "    val_labels_pred = (val_labels_pred > 0.5).astype(int)  # Convert probabilities to class labels\n",
    "    \n",
    "    all_true_labels.extend(val_labels_true)\n",
    "    all_pred_labels.extend(val_labels_pred.flatten())\n",
    "\n",
    "    # Print the classification report for this fold\n",
    "    print(\"Classification Report for Fold\", fold + 1)\n",
    "    print(classification_report(val_labels_true, val_labels_pred))\n",
    "\n",
    "# Average performance across folds\n",
    "print(f\"Average accuracy across 4 folds: {np.mean(fold_accuracies):.4f}\")\n",
    "\n",
    "# Overall classification report across all folds\n",
    "print(\"Overall Classification Report\")\n",
    "print(classification_report(all_true_labels, all_pred_labels))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bfd1e402",
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "from sklearn.model_selection import StratifiedKFold\n",
    "from sklearn.utils import class_weight\n",
    "from tensorflow.keras.preprocessing.image import ImageDataGenerator\n",
    "from tensorflow.keras.applications.resnet50 import preprocess_input\n",
    "from tensorflow.keras.models import Model\n",
    "from tensorflow.keras.layers import Dense, Flatten, Dropout\n",
    "from tensorflow.keras.optimizers import SGD\n",
    "from tensorflow.keras.applications.resnet50 import ResNet50\n",
    "from sklearn.metrics import classification_report, confusion_matrix\n",
    "\n",
    "# Paths to the directories\n",
    "original_dir2 = os.path.abspath('C:\\\\Users\\\\oscar\\\\Documents\\\\experimentos\\\\cocos\\\\kfold')\n",
    "train_croc_dir = os.path.join(original_dir2, 'CS1')\n",
    "train_cm_dir = os.path.join(original_dir2, 'SF')\n",
    "\n",
    "# Collect all image paths and corresponding labels\n",
    "all_image_paths = []\n",
    "all_labels = []\n",
    "\n",
    "for class_name, class_dir in [('CS1', train_croc_dir), ('SF', train_cm_dir)]:\n",
    "    for image_file in os.listdir(class_dir):\n",
    "        all_image_paths.append(os.path.join(class_dir, image_file))\n",
    "        all_labels.append(0 if class_name == 'CS1' else 1)\n",
    "\n",
    "# Convert to NumPy arrays\n",
    "all_image_paths = np.array(all_image_paths)\n",
    "all_labels = np.array(all_labels)\n",
    "\n",
    "# K-Fold Cross-Validation (Stratified)\n",
    "skf = StratifiedKFold(n_splits=4, shuffle=True, random_state=42)  # Change to nsplits=5 for a 80-20 split\n",
    "\n",
    "# Data generators\n",
    "datagen = ImageDataGenerator(preprocessing_function=preprocess_input)\n",
    "\n",
    "# Define the model\n",
    "def define_model():\n",
    "    # Load ResNet50 without the top layer\n",
    "    model = ResNet50(include_top=False, input_shape=(80, 400, 3))\n",
    "    \n",
    "    # Mark the layers of ResNet50 as not trainable\n",
    "    for layer in model.layers:\n",
    "        layer.trainable = False\n",
    "    \n",
    "    # Add custom layers for classification\n",
    "    flat1 = Flatten()(model.layers[-1].output)\n",
    "    class1 = Dense(128, activation='relu', kernel_initializer='he_uniform')(flat1)\n",
    "    drop = Dropout(0.3)(class1)\n",
    "    output = Dense(2, activation='softmax')(drop)  # For 2 classes, use softmax\n",
    "    \n",
    "    # Define the new model\n",
    "    model = Model(inputs=model.inputs, outputs=output)\n",
    "    \n",
    "    # Compile the model with SGD optimizer\n",
    "    opt = SGD(learning_rate=0.001, momentum=0.9)\n",
    "    model.compile(optimizer=opt, loss='categorical_crossentropy', metrics=['accuracy'])\n",
    "    \n",
    "    return model\n",
    "\n",
    "# Initialize lists for metrics and labels\n",
    "fold_accuracies = []\n",
    "all_true_labels = []\n",
    "all_pred_labels = []\n",
    "\n",
    "# Loop over each fold\n",
    "for fold, (train_idx, val_idx) in enumerate(skf.split(all_image_paths, all_labels)):\n",
    "    print(f\"Training Fold {fold + 1}/4...\")\n",
    "    \n",
    "    # Split data into training and validation sets\n",
    "    train_images = all_image_paths[train_idx]\n",
    "    train_labels = all_labels[train_idx]\n",
    "    val_images = all_image_paths[val_idx]\n",
    "    val_labels = all_labels[val_idx]\n",
    "    \n",
    "    # Calculate class weights to handle class imbalance\n",
    "    class_weights = class_weight.compute_class_weight('balanced', classes=np.unique(all_labels), y=train_labels)\n",
    "    class_weight_dict = dict(enumerate(class_weights))\n",
    "    \n",
    "    # Convert labels to strings (for flow_from_dataframe compatibility)\n",
    "    train_labels_str = train_labels.astype(str)\n",
    "    val_labels_str = val_labels.astype(str)\n",
    "\n",
    "    # Create training generator\n",
    "    train_generator = datagen.flow_from_dataframe(\n",
    "        dataframe=pd.DataFrame({'filename': train_images, 'class': train_labels_str}),\n",
    "        x_col='filename',\n",
    "        y_col='class',\n",
    "        target_size=(80, 400),\n",
    "        batch_size=64,\n",
    "        class_mode='categorical'\n",
    "    )\n",
    "    \n",
    "    # Create validation generator\n",
    "    validation_generator = datagen.flow_from_dataframe(\n",
    "        dataframe=pd.DataFrame({'filename': val_images, 'class': val_labels_str}),\n",
    "        x_col='filename',\n",
    "        y_col='class',\n",
    "        target_size=(80, 400),\n",
    "        batch_size=32,\n",
    "        class_mode='categorical',\n",
    "        shuffle=False\n",
    "    )\n",
    "    \n",
    "    # Define the model for this fold\n",
    "    model = define_model()\n",
    "\n",
    "    # Train the model\n",
    "    history = model.fit(\n",
    "        train_generator,\n",
    "        steps_per_epoch=np.ceil(len(train_images) / 64),\n",
    "        epochs=10,\n",
    "        validation_data=validation_generator,\n",
    "        validation_steps=np.ceil(len(val_images) / 32),\n",
    "        class_weight=class_weight_dict,  # Use class weights to handle class imbalance\n",
    "        verbose=1\n",
    "    )\n",
    "    \n",
    "    # Evaluate the model\n",
    "    val_loss, val_accuracy = model.evaluate(validation_generator, verbose=0)\n",
    "    fold_accuracies.append(val_accuracy)\n",
    "    print(f\"Fold {fold + 1} accuracy: {val_accuracy:.4f}\")\n",
    "    \n",
    "    # Get true and predicted labels\n",
    "    val_labels_true = validation_generator.classes  # True labels from the generator\n",
    "    val_labels_pred = model.predict(validation_generator)  # Predict probabilities\n",
    "    val_labels_pred_classes = np.argmax(val_labels_pred, axis=1)  # Convert probabilities to class predictions\n",
    "    \n",
    "    # Extend overall true and predicted labels\n",
    "    all_true_labels.extend(val_labels_true)\n",
    "    all_pred_labels.extend(val_labels_pred_classes)\n",
    "\n",
    "    # Confusion matrix and classification report for this fold\n",
    "    print(f\"Classification Report for Fold {fold + 1}\")\n",
    "    print(classification_report(val_labels_true, val_labels_pred_classes))\n",
    "\n",
    "    print(f\"Confusion Matrix for Fold {fold + 1}\")\n",
    "    print(confusion_matrix(val_labels_true, val_labels_pred_classes))\n",
    "\n",
    "# Average accuracy across folds\n",
    "print(f\"\\nAverage accuracy across 4 folds: {np.mean(fold_accuracies):.4f}\")\n",
    "\n",
    "# Overall classification report across all folds\n",
    "print(\"\\nOverall Classification Report\")\n",
    "print(classification_report(all_true_labels, all_pred_labels))\n",
    "\n",
    "# Overall confusion matrix\n",
    "print(\"\\nOverall Confusion Matrix\")\n",
    "print(confusion_matrix(all_true_labels, all_pred_labels))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f87322ca",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "121c2191",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "68193163",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f11a3658",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1536eb31",
   "metadata": {},
   "outputs": [],
   "source": [
    "#STAGE 4: FSSL"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "38fdd856",
   "metadata": {},
   "outputs": [],
   "source": [
    "import tensorflow as tf\n",
    "from tensorflow.keras import layers, models, optimizers\n",
    "from tensorflow.keras.applications import VGG16\n",
    "from tensorflow.keras.applications import resnet50\n",
    "from sklearn.model_selection import train_test_split\n",
    "import numpy as np\n",
    "import os\n",
    "from glob import glob\n",
    "from tensorflow.keras.applications.resnet50 import preprocess_input #224 x 224\n",
    "#from tensorflow.keras.applications.densenet import preprocess_input #224 x 224\n",
    "from tensorflow.keras.preprocessing.image import load_img, img_to_array\n",
    "\n",
    "# Set your directory containing the images\n",
    "data_directory = 'C:\\\\Users\\\\oscar\\\\Documents\\\\experimentos\\\\cocos\\\\kfold'\n",
    "\n",
    "# Function to load and resize images from a directory\n",
    "\n",
    "def load_images(directory, target_size=(80, 400)):\n",
    "    image_list = []\n",
    "    file_pattern = os.path.join(directory, '*.bmp')  # Modify as needed for other file types\n",
    "    for filename in glob(file_pattern):\n",
    "        print(f\"Loading, resizing, and preprocessing image: {filename}\")\n",
    "        img = load_img(filename, target_size=target_size)\n",
    "        img_array = img_to_array(img)\n",
    "        img_array = preprocess_input(img_array)  # Apply TL model preprocessing\n",
    "        image_list.append(img_array)\n",
    "    return np.array(image_list)\n",
    "\n",
    "\n",
    "# Load images for each class\n",
    "class1_images = load_images(os.path.join(data_directory, 'CS1'))\n",
    "class2_images = load_images(os.path.join(data_directory, 'SF'))\n",
    "#class3_images = load_images(os.path.join(data_directory, 'oryx'))\n",
    "\n",
    "# Create labels for each class\n",
    "class1_labels = np.zeros(len(class1_images))\n",
    "class2_labels = np.ones(len(class2_images))\n",
    "#class3_labels = 2 * np.ones(len(class3_images))\n",
    "\n",
    "# Concatenate images and labels for all classes\n",
    "all_images = np.concatenate([class1_images, class2_images])\n",
    "all_labels = np.concatenate([class1_labels, class2_labels])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 78,
   "id": "4d45818c",
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "# Split the data into training (70%) and the rest (30%)\n",
    "train_images, temp_images, train_labels, temp_labels = train_test_split(\n",
    "    all_images, all_labels, test_size=0.3, random_state=42\n",
    ")\n",
    "\n",
    "# Split the remaining 30% into validation (15%) and testing (15%)\n",
    "val_images, test_images, val_labels, test_labels = train_test_split(\n",
    "    temp_images, temp_labels, test_size=0.5, random_state=42\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "24905f89",
   "metadata": {},
   "outputs": [],
   "source": [
    "num_training_images = len(train_images)\n",
    "print(f'Number of training images: {num_training_images}')\n",
    "\n",
    "num_validation_images = len(val_images)\n",
    "print(f'Number of validation images: {num_validation_images}')\n",
    "\n",
    "num_testing_images = len(test_images)\n",
    "print(f'Number of testing images: {num_testing_images}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 80,
   "id": "6ae74c78",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Load the pre-trained ResNet50 model without the top classification layers\n",
    "base_model = tf.keras.applications.ResNet50(weights='imagenet', include_top=False, input_shape=(80, 400, 3))\n",
    "\n",
    "# Freeze the convolutional layers of the ResNet50 base model\n",
    "for layer in base_model.layers:\n",
    "    layer.trainable = False"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b4394bab",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 81,
   "id": "7e39eed4",
   "metadata": {},
   "outputs": [],
   "source": [
    "#Simple model\n",
    "\n",
    "from tensorflow.keras.callbacks import EarlyStopping, LearningRateScheduler\n",
    "from tensorflow.keras.preprocessing.image import ImageDataGenerator\n",
    "import tensorflow as tf\n",
    "import numpy as np\n",
    "import random\n",
    "\n",
    "# Set random seed for TensorFlow\n",
    "tf.random.set_seed(42)\n",
    "\n",
    "# Set random seed for numpy\n",
    "np.random.seed(42)\n",
    "\n",
    "# Set random seed for Python random module\n",
    "random.seed(42)\n",
    "\n",
    "\n",
    "# Now your entire script will produce reproducible results as long as it doesn't rely on non-deterministic operations\n",
    "\n",
    "# Create MAML model\n",
    "def create_maml_model(base_model, num_classes):\n",
    "    maml_model = models.Sequential([\n",
    "        base_model,\n",
    "        layers.Conv2D(512, (3, 3), activation='relu'),\n",
    "        layers.GlobalAveragePooling2D(),  # Replace max-pooling with GlobalAveragePooling\n",
    "        layers.Dense(512, activation='relu'),\n",
    "        layers.Dropout(0.5),\n",
    "        layers.BatchNormalization(),\n",
    "        layers.Dense(num_classes, activation='softmax')\n",
    "    ])\n",
    "    return maml_model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d9ec169a",
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "# Set up MAML model\n",
    "num_classes = 2  # Number of output classes\n",
    "maml_model = create_maml_model(base_model, num_classes)\n",
    "\n",
    "# Set up MAML optimizer\n",
    "meta_optimizer = optimizers.Adam(learning_rate=1e-3)\n",
    "#meta_optimizer = optimizers.Adagrad(learning_rate=0.001)\n",
    "#meta_optimizer = optimizers.SGD(learning_rate=0.001, momentum=0.9)\n",
    "\n",
    "# Compile the MAML model\n",
    "maml_model.compile(optimizer=meta_optimizer, loss='sparse_categorical_crossentropy', metrics=['accuracy'])\n",
    "\n",
    "\n",
    "# Lists to store training, validation, and testing history\n",
    "train_loss_history = []\n",
    "train_acc_history = []\n",
    "val_loss_history = []\n",
    "val_acc_history = []\n",
    "test_loss_history = []\n",
    "test_acc_history = []\n",
    "\n",
    "# Training loop for few-shot learning\n",
    "num_epochs = 100  # Increase the number of epochs\n",
    "num_tasks = 19  # Number of few-shot tasks\n",
    "validation_interval = 1  # Evaluate on the validation set every 'validation_interval' epochs\n",
    "num_shots = 10  # Number of examples per class in each few-shot task\n",
    "\n",
    "# Early stopping parameters\n",
    "patience = 15\n",
    "best_val_loss = float('inf')\n",
    "wait = 0\n",
    "\n",
    "# Data Augmentation\n",
    "datagen = ImageDataGenerator(\n",
    "    rotation_range=20,\n",
    "    width_shift_range=0.2,\n",
    "    height_shift_range=0.2,\n",
    "    horizontal_flip=True\n",
    ")\n",
    "\n",
    "# Callbacks\n",
    "early_stopping = tf.keras.callbacks.EarlyStopping(\n",
    "    monitor='val_loss',\n",
    "    patience=patience,\n",
    "    restore_best_weights=True\n",
    ")\n",
    "# Define the directory where you want to save the best model\n",
    "save_dir = 'C:\\\\Users\\\\oscar\\\\Documents\\\\experimentos\\\\cocos\\\\kfold'\n",
    "\n",
    "# Callback to save the best model\n",
    "model_checkpoint = tf.keras.callbacks.ModelCheckpoint(\n",
    "    os.path.join(save_dir, 'best_Resn_model5_15.keras'),  # Full path to save the best model\n",
    "    monitor='val_loss',\n",
    "    save_best_only=True\n",
    ")\n",
    "\n",
    "\n",
    "# Learning rate scheduler function\n",
    "def lr_schedule(epoch):\n",
    "    lr = 1e-3\n",
    "    if epoch > 10:\n",
    "        lr *= 0.1\n",
    "    if epoch > 20:\n",
    "        lr *= 0.1\n",
    "    if epoch > 30:\n",
    "        lr *= 0.1\n",
    "    return lr\n",
    "\n",
    "# Training loop\n",
    "for epoch in range(num_epochs):\n",
    "    for task in range(num_tasks):\n",
    "        task_samples = []\n",
    "        task_labels = []\n",
    "        for class_idx in range(num_classes):\n",
    "            class_indices = np.where(train_labels == class_idx)[0]\n",
    "            selected_indices = np.random.choice(class_indices, num_shots, replace=False)\n",
    "            selected_samples = train_images[selected_indices]\n",
    "            task_samples.extend(selected_samples)\n",
    "            task_labels.extend([class_idx] * num_shots)\n",
    "\n",
    "        task_samples = np.array(task_samples)\n",
    "        task_labels = np.array(task_labels)\n",
    "\n",
    "        # Fine-tune the model on the few-shot task with data augmentation\n",
    "        with tf.GradientTape() as tape:\n",
    "            augmented_task_samples = []\n",
    "            for sample in task_samples:\n",
    "                augmented_sample = datagen.random_transform(sample)\n",
    "                augmented_task_samples.append(augmented_sample)\n",
    "            augmented_task_samples = np.array(augmented_task_samples)\n",
    "\n",
    "            logits = maml_model(augmented_task_samples)\n",
    "            loss = tf.losses.sparse_categorical_crossentropy(task_labels, logits)\n",
    "\n",
    "        gradients = tape.gradient(loss, maml_model.trainable_variables)\n",
    "        meta_optimizer.apply_gradients(zip(gradients, maml_model.trainable_variables))\n",
    "\n",
    "    train_loss, train_acc = maml_model.evaluate(train_images, train_labels, verbose=0)\n",
    "    print(f'Epoch {epoch + 1}/{num_epochs} - Train Loss: {train_loss:.4f}, Train Accuracy: {train_acc * 100:.2f}%')\n",
    "\n",
    "    train_loss_history.append(train_loss)\n",
    "    train_acc_history.append(train_acc)\n",
    "\n",
    "    if epoch % validation_interval == 0:\n",
    "        val_loss, val_acc = maml_model.evaluate(val_images, val_labels, verbose=0)\n",
    "        print(f'Epoch {epoch + 1}/{num_epochs} - Validation Loss: {val_loss:.4f}, Validation Accuracy: {val_acc * 100:.2f}%')\n",
    "        \n",
    "        val_loss_history.append(val_loss)\n",
    "        val_acc_history.append(val_acc)\n",
    "\n",
    "        if val_loss < best_val_loss:\n",
    "            best_val_loss = val_loss\n",
    "            wait = 0\n",
    "            # Save the best model\n",
    "            maml_model.save(os.path.join(save_dir, 'best_model.h5'))#change model name\n",
    "        else:\n",
    "            wait += 1\n",
    "            if wait >= patience:\n",
    "                print(f'Early stopping at epoch {epoch + 1}')\n",
    "                break\n",
    "\n",
    "        # Apply learning rate scheduler\n",
    "    lr = lr_schedule(epoch)\n",
    "    tf.keras.backend.set_value(meta_optimizer.lr, lr)\n",
    "    print(f'Learning Rate: {lr}')\n",
    "\n",
    "# After training, you can use the test set for the final evaluation\n",
    "test_loss, test_acc = maml_model.evaluate(test_images, test_labels, verbose=0)\n",
    "print(f'Final Testing Loss: {test_loss:.4f}, Testing Accuracy: {test_acc * 100:.2f}%')\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "013acf0b",
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "# Plotting the training and validation history\n",
    "plt.figure(figsize=(12, 4))\n",
    "plt.subplot(1, 2, 1)\n",
    "plt.plot(train_loss_history, label='Train Loss')\n",
    "plt.plot(val_loss_history, label='Validation Loss')\n",
    "plt.title('Training and Validation Loss')\n",
    "plt.xlabel('Epochs')\n",
    "plt.ylabel('Loss')\n",
    "plt.legend()\n",
    "\n",
    "plt.subplot(1, 2, 2)\n",
    "plt.plot(train_acc_history, label='Train Accuracy')\n",
    "plt.plot(val_acc_history, label='Validation Accuracy')\n",
    "plt.title('Training and Validation Accuracy')\n",
    "plt.xlabel('Epochs')\n",
    "plt.ylabel('Accuracy')\n",
    "plt.legend()\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "abe9bd49",
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.metrics import classification_report\n",
    "\n",
    "# After training, you can use the test set for the final evaluation\n",
    "test_loss, test_acc = maml_model.evaluate(test_images, test_labels, verbose=0)\n",
    "print(f'Final Testing Loss: {test_loss:.4f}, Testing Accuracy: {test_acc * 100:.2f}%')\n",
    "\n",
    "# Predict probabilities for test images\n",
    "y_pred_prob = maml_model.predict(test_images)\n",
    "\n",
    "# Convert probabilities to class labels\n",
    "y_pred = np.argmax(y_pred_prob, axis=1)\n",
    "\n",
    "# Generate classification report\n",
    "print(classification_report(test_labels, y_pred))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2cefb9fa-8520-4ab0-b184-6ae9218c15ec",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c65cad39-7685-4485-be87-6de4c9595023",
   "metadata": {},
   "outputs": [],
   "source": [
    "#STAGE 5: MAML"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "983f4f49-1dbf-4db5-8865-74cfec668ae5",
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import numpy as np\n",
    "import random\n",
    "import tensorflow as tf\n",
    "from tensorflow.keras import layers, models, optimizers\n",
    "from tensorflow.keras.preprocessing.image import ImageDataGenerator\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "# Set random seeds for reproducibility\n",
    "tf.random.set_seed(42)\n",
    "np.random.seed(42)\n",
    "random.seed(42)\n",
    "\n",
    "# Define base model using ResNet50\n",
    "base_model = tf.keras.applications.ResNet50(weights='imagenet', include_top=False, input_shape=(80, 400, 3))\n",
    "for layer in base_model.layers:\n",
    "    layer.trainable = False\n",
    "\n",
    "# Unfreeze last few ResNet50 layers for better adaptation\n",
    "for layer in base_model.layers[-10:]:\n",
    "    layer.trainable = True\n",
    "\n",
    "def create_maml_model(base_model, num_classes):\n",
    "    maml_model = models.Sequential([\n",
    "        base_model,\n",
    "        layers.Conv2D(512, (3, 3), activation='relu', padding='same'),\n",
    "        layers.GlobalAveragePooling2D(),\n",
    "        layers.Dense(512, activation='relu'),\n",
    "        layers.Dropout(0.5),\n",
    "        layers.BatchNormalization(),\n",
    "        layers.Dense(num_classes, activation='softmax')\n",
    "    ])\n",
    "    return maml_model\n",
    "\n",
    "# MAML hyperparameters\n",
    "num_classes = 2\n",
    "n_way = num_classes\n",
    "k_shot = 5\n",
    "q_query = 5\n",
    "num_tasks = 20\n",
    "num_epochs = 100\n",
    "alpha = 0.01\n",
    "beta = 0.001\n",
    "inner_steps = 5\n",
    "\n",
    "# Instantiate model and optimizer\n",
    "maml_model = create_maml_model(base_model, num_classes)\n",
    "meta_optimizer = tf.keras.optimizers.Adam(learning_rate=beta)\n",
    "\n",
    "# Paths for saving\n",
    "save_dir = 'C:\\\\Users\\\\oscar\\\\Documents\\\\experimentos\\\\Dataset2\\\\kfold'\n",
    "os.makedirs(save_dir, exist_ok=True)\n",
    "\n",
    "# Data augmentation\n",
    "datagen = ImageDataGenerator(\n",
    "    rotation_range=20,\n",
    "    width_shift_range=0.2,\n",
    "    height_shift_range=0.2,\n",
    "    horizontal_flip=True\n",
    ")\n",
    "\n",
    "def maml_task_step(model, support_images, support_labels, query_images, query_labels):\n",
    "    with tf.GradientTape() as meta_tape:\n",
    "        # Clone the weights from the model for inner loop\n",
    "        fast_weights = model.trainable_variables\n",
    "\n",
    "        # Inner loop adaptation\n",
    "        for _ in range(inner_steps):\n",
    "            with tf.GradientTape() as tape:\n",
    "                support_preds = model(support_images, training=True)\n",
    "                support_loss = tf.keras.losses.sparse_categorical_crossentropy(support_labels, support_preds)\n",
    "                support_loss = tf.reduce_mean(support_loss)\n",
    "            grads = tape.gradient(support_loss, fast_weights)\n",
    "            fast_weights = [w - alpha * g if g is not None else w for w, g in zip(fast_weights, grads)]\n",
    "\n",
    "        # Compute query predictions using the adapted weights\n",
    "        def forward_with_weights(x, weights):\n",
    "            idx = 0\n",
    "            for layer in model.layers:\n",
    "                if hasattr(layer, 'trainable_variables') and layer.trainable_variables:\n",
    "                    n_vars = len(layer.trainable_variables)\n",
    "                    for i in range(n_vars):\n",
    "                        layer.trainable_variables[i].assign(weights[idx])\n",
    "                        idx += 1\n",
    "            return model(x, training=True)\n",
    "\n",
    "        query_preds = forward_with_weights(query_images, fast_weights)\n",
    "        query_loss = tf.keras.losses.sparse_categorical_crossentropy(query_labels, query_preds)\n",
    "        query_loss = tf.reduce_mean(query_loss)\n",
    "\n",
    "    meta_grads = meta_tape.gradient(query_loss, model.trainable_variables)\n",
    "    return meta_grads, query_loss\n",
    "\n",
    "# Track loss and accuracy for plotting\n",
    "train_loss_history = []\n",
    "val_loss_history = []\n",
    "train_acc_history = []\n",
    "val_acc_history = []\n",
    "\n",
    "# MAML training loop\n",
    "best_val_loss = float('inf')\n",
    "patience = 15\n",
    "wait = 0\n",
    "\n",
    "for epoch in range(num_epochs):\n",
    "    print(f\"\\nEpoch {epoch + 1}/{num_epochs}\")\n",
    "    meta_grads_accum = [tf.zeros_like(var) for var in maml_model.trainable_variables]\n",
    "    epoch_loss = []\n",
    "\n",
    "    for task in range(num_tasks):\n",
    "        support_images, support_labels, query_images, query_labels = [], [], [], []\n",
    "\n",
    "        for class_idx in range(n_way):\n",
    "            class_indices = np.where(train_labels == class_idx)[0]\n",
    "            selected_indices = np.random.choice(class_indices, k_shot + q_query, replace=False)\n",
    "            support_idx = selected_indices[:k_shot]\n",
    "            query_idx = selected_indices[k_shot:]\n",
    "\n",
    "            support_images.extend(train_images[support_idx])\n",
    "            support_labels.extend([class_idx] * k_shot)\n",
    "            query_images.extend(train_images[query_idx])\n",
    "            query_labels.extend([class_idx] * q_query)\n",
    "\n",
    "        support_images = tf.convert_to_tensor(np.array(support_images), dtype=tf.float32)\n",
    "        support_labels = tf.convert_to_tensor(np.array(support_labels), dtype=tf.int32)\n",
    "        query_images = tf.convert_to_tensor(np.array(query_images), dtype=tf.float32)\n",
    "        query_labels = tf.convert_to_tensor(np.array(query_labels), dtype=tf.int32)\n",
    "\n",
    "        meta_grads, loss = maml_task_step(\n",
    "            maml_model, support_images, support_labels, query_images, query_labels\n",
    "        )\n",
    "\n",
    "        meta_grads_accum = [\n",
    "            acc + (g if g is not None else tf.zeros_like(acc))\n",
    "            for acc, g in zip(meta_grads_accum, meta_grads)\n",
    "        ]\n",
    "        epoch_loss.append(loss.numpy())\n",
    "\n",
    "    mean_grads = [g / num_tasks for g in meta_grads_accum]\n",
    "    meta_optimizer.apply_gradients(zip(mean_grads, maml_model.trainable_variables))\n",
    "\n",
    "    mean_query_loss = np.mean(epoch_loss)\n",
    "    train_loss_history.append(mean_query_loss)\n",
    "\n",
    "    train_preds = maml_model.predict(train_images)\n",
    "    train_acc = np.mean(np.argmax(train_preds, axis=1) == train_labels)\n",
    "    train_acc_history.append(train_acc)\n",
    "\n",
    "    print(f\"Epoch {epoch + 1}: Mean Query Loss: {mean_query_loss:.4f}\")\n",
    "\n",
    "    val_preds = maml_model.predict(val_images)\n",
    "    val_loss = tf.reduce_mean(tf.keras.losses.sparse_categorical_crossentropy(val_labels, val_preds)).numpy()\n",
    "    val_acc = np.mean(np.argmax(val_preds, axis=1) == val_labels)\n",
    "\n",
    "    val_loss_history.append(val_loss)\n",
    "    val_acc_history.append(val_acc)\n",
    "\n",
    "    print(f\"Validation Loss: {val_loss:.4f}, Accuracy: {val_acc * 100:.2f}%\")\n",
    "\n",
    "    if val_loss < best_val_loss:\n",
    "        best_val_loss = val_loss\n",
    "        wait = 0\n",
    "        maml_model.save(os.path.join(save_dir, 'best_model.keras'))\n",
    "    else:\n",
    "        wait += 1\n",
    "        if wait >= patience:\n",
    "            print(f\"Early stopping at epoch {epoch + 1}\")\n",
    "            break\n",
    "\n",
    "# Final test\n",
    "test_preds = maml_model.predict(test_images)\n",
    "test_loss = tf.reduce_mean(tf.keras.losses.sparse_categorical_crossentropy(test_labels, test_preds)).numpy()\n",
    "test_acc = np.mean(np.argmax(test_preds, axis=1) == test_labels)\n",
    "print(f'Final Test Loss: {test_loss:.4f}, Accuracy: {test_acc * 100:.2f}%')\n",
    "\n",
    "# Plotting the training and validation history\n",
    "plt.figure(figsize=(12, 4))\n",
    "plt.subplot(1, 2, 1)\n",
    "plt.plot(train_loss_history, label='Train Loss', color='blue')\n",
    "plt.plot(val_loss_history, label='Validation Loss', color='orange')\n",
    "plt.title('Training and Validation Loss')\n",
    "plt.xlabel('Epochs')\n",
    "plt.ylabel('Loss')\n",
    "plt.legend()\n",
    "\n",
    "plt.subplot(1, 2, 2)\n",
    "plt.plot(train_acc_history, label='Train Accuracy', color='blue')\n",
    "plt.plot(val_acc_history, label='Validation Accuracy', color='orange')\n",
    "plt.title('Training and Validation Accuracy')\n",
    "plt.xlabel('Epochs')\n",
    "plt.ylabel('Accuracy')\n",
    "plt.legend()\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ee707ec9-86a5-4c15-b7ac-cae2a1fd458a",
   "metadata": {},
   "outputs": [],
   "source": [
    "#confusion matrix\n",
    "from sklearn.metrics import classification_report, confusion_matrix, ConfusionMatrixDisplay\n",
    "\n",
    "test_preds = maml_model.predict(test_images)\n",
    "test_pred_labels = np.argmax(test_preds, axis=1)\n",
    "\n",
    "test_loss = tf.reduce_mean(tf.keras.losses.sparse_categorical_crossentropy(test_labels, test_preds)).numpy()\n",
    "test_acc = np.mean(test_pred_labels == test_labels)\n",
    "print(f'Final Test Loss: {test_loss:.4f}, Accuracy: {test_acc * 100:.2f}%')\n",
    "\n",
    "# Classification report\n",
    "from sklearn.metrics import classification_report\n",
    "print(\"\\nClassification Report:\")\n",
    "print(classification_report(test_labels, test_pred_labels, target_names=['CS', 'SF']))\n",
    "\n",
    "# Confusion matrix\n",
    "cm = confusion_matrix(test_labels, test_pred_labels)\n",
    "disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=['CS', 'SF'])\n",
    "disp.plot(cmap='Blues', values_format='d')\n",
    "plt.title(\"Confusion Matrix\")\n",
    "plt.show()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
